Merge "Fix "Add new" strings in contact editor"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 98a864a..ff7c089 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -467,6 +467,7 @@
         <activity android:name="CallDetailActivity"
             android:label="@string/callDetailTitle"
             android:theme="@style/CallDetailActivityTheme"
+            android:screenOrientation="nosensor"
             android:taskAffinity="android.task.contacts.phone"
         >
             <intent-filter>
diff --git a/res/layout-sw580dp-w1000dp/contact_detail_container.xml b/res/layout-sw580dp-w1000dp/contact_detail_container.xml
index b6a6319..be91296 100644
--- a/res/layout-sw580dp-w1000dp/contact_detail_container.xml
+++ b/res/layout-sw580dp-w1000dp/contact_detail_container.xml
@@ -30,12 +30,11 @@
         android:id="@+id/about_fragment"
         android:layout_width="0dip"
         android:layout_height="match_parent"
-        android:layout_weight="3" />
+        android:layout_weight="1" />
 
     <fragment class="com.android.contacts.detail.ContactDetailUpdatesFragment"
         android:id="@+id/updates_fragment"
-        android:layout_width="0dip"
-        android:layout_height="match_parent"
-        android:layout_weight="2" />
+        android:layout_width="306dip"
+        android:layout_height="match_parent" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout-sw580dp/people_activity.xml b/res/layout-sw580dp/people_activity.xml
index d3d7c32..87bb3b5 100644
--- a/res/layout-sw580dp/people_activity.xml
+++ b/res/layout-sw580dp/people_activity.xml
@@ -69,7 +69,7 @@
             ex:layout_narrowParentWidth="800dip"
             ex:layout_narrowMarginRight="0dip"
             ex:layout_wideParentWidth="1280dip"
-            ex:layout_wideMarginRight="48dip"
+            ex:layout_wideMarginRight="0dip"
             ex:clipMarginLeft="0dip"
             ex:clipMarginTop="3dip"
             ex:clipMarginRight="3dip"
diff --git a/res/layout/carousel_updates_tab.xml b/res/layout/carousel_updates_tab.xml
index d235280..d453dae 100644
--- a/res/layout/carousel_updates_tab.xml
+++ b/res/layout/carousel_updates_tab.xml
@@ -21,13 +21,22 @@
     android:layout_weight="1"
     android:background="@color/detail_tab_background">
 
-    <!-- Transparent view to overlay on the contact's photo
+    <ImageView android:id="@+id/status_photo"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+        android:visibility="gone" />
+
+
+    <!-- Transparent view to overlay on the update photo
     (to allow white text to appear over a white photo). -->
     <View
         android:layout_width="match_parent"
         android:layout_height="@dimen/detail_tab_carousel_tab_label_height"
         android:layout_alignParentLeft="true"
         android:layout_alignParentBottom="true"
+        android:layout_above="@id/status_photo"
         android:background="@android:color/black"
         android:alpha=".25"/>
 
@@ -37,6 +46,7 @@
         android:layout_height="@dimen/detail_tab_carousel_tab_label_height"
         android:layout_alignParentLeft="true"
         android:layout_alignParentBottom="true"
+        android:layout_above="@id/status_photo"
         android:paddingLeft="@dimen/detail_item_side_margin"
         android:singleLine="true"
         android:gravity="left|center_vertical"
diff --git a/res/layout/contact_detail_updates_fragment.xml b/res/layout/contact_detail_updates_fragment.xml
index 36a40c3..92f3575 100644
--- a/res/layout/contact_detail_updates_fragment.xml
+++ b/res/layout/contact_detail_updates_fragment.xml
@@ -19,40 +19,31 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <!--
-      TODO: Make this a list of social updates instead of just showing one update. Wait
-      until the social integration is done in the provider so that we know the
-      best way to setup the adapter.
-    -->
-    <LinearLayout
-        android:orientation="vertical"
+    <ScrollView
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:paddingTop="@dimen/detail_tab_carousel_height"
-        android:background="@color/background_primary">
+        android:background="@color/background_social_updates">
 
-        <include
-            android:id="@+id/title"
-            layout="@layout/contact_detail_kind_title_entry_view"
-            android:paddingTop="@dimen/detail_item_vertical_margin" />
-
-        <TextView android:id="@+id/status"
-            android:layout_width="wrap_content"
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:paddingTop="@dimen/detail_item_vertical_margin"
-            android:paddingLeft="@dimen/detail_item_side_margin"
-            android:paddingRight="@dimen/detail_item_side_margin"
-            android:textAppearance="?android:attr/textAppearanceMedium"/>
+            android:paddingTop="@dimen/detail_update_section_top_padding">
 
-        <TextView android:id="@+id/status_date"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingLeft="@dimen/detail_item_side_margin"
-            android:paddingRight="@dimen/detail_item_side_margin"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:attr/textColorTertiary"/>
+            <include
+                android:id="@+id/title"
+                layout="@layout/contact_detail_kind_title_entry_view" />
 
-    </LinearLayout>
+            <LinearLayout
+                android:id="@+id/update_list"
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingLeft="@dimen/detail_update_section_side_padding"
+                android:paddingRight="@dimen/detail_update_section_side_padding" />
+        </LinearLayout>
+    </ScrollView>
 
     <View
         android:id="@+id/alpha_overlay"
diff --git a/res/layout/contacts_list_content.xml b/res/layout/contacts_list_content.xml
index b92929b..490499c 100644
--- a/res/layout/contacts_list_content.xml
+++ b/res/layout/contacts_list_content.xml
@@ -14,37 +14,54 @@
      limitations under the License.
 -->
 
-<LinearLayout
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/pinned_header_list_layout"
+    android:layout_marginTop="@dimen/contact_browser_list_top_margin"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-    <TextView
-        android:id="@+id/account_filter_header"
+    android:layout_height="match_parent" >
+    <LinearLayout
         android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/account_filter_header"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:gravity="center"
+            android:padding="5dip"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="@android:color/white"
+            android:background="@android:color/black"
+            android:visibility="gone" />
+
+        <view
+            class="com.android.contacts.list.ContactEntryListView"
+            android:id="@android:id/list"
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:fastScrollEnabled="true"
+            android:layout_weight="1" />
+
+        <ViewStub
+            android:id="@+id/footer_stub"
+            android:layout="@layout/footer_panel"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+    <TextView
+        android:id="@+id/contacts_count"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_marginRight="@dimen/contacts_count_right_margin"
         android:singleLine="true"
         android:ellipsize="end"
-        android:gravity="center"
-        android:padding="5dip"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="@android:color/white"
-        android:background="@android:color/black"
-        android:visibility="gone" />
-
-    <view
-        class="com.android.contacts.list.ContactEntryListView"
-        android:id="@android:id/list"
-        android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:fastScrollEnabled="true"
-        android:layout_weight="1" />
-
-    <ViewStub
-        android:id="@+id/footer_stub"
-        android:layout="@layout/footer_panel"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content" />
-</LinearLayout>
+        android:gravity="right"
+        android:layout_gravity="top|right"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="@color/contact_count_text_color"
+        android:background="@color/contact_browser_list_bk_color" />
+</FrameLayout>
diff --git a/res/layout/stream_item_one_column.xml b/res/layout/stream_item_one_column.xml
new file mode 100644
index 0000000..014e3f1
--- /dev/null
+++ b/res/layout/stream_item_one_column.xml
@@ -0,0 +1,36 @@
+<?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:paddingTop="@dimen/detail_update_section_item_vertical_padding"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/stream_item_content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingBottom="@dimen/detail_update_section_item_vertical_padding"
+        android:paddingLeft="@dimen/detail_update_section_item_left_padding"
+        android:orientation="vertical" />
+
+    <View
+        android:id="@+id/horizontal_divider"
+        android:layout_width="match_parent"
+        android:layout_height="1px"
+        android:background="?android:attr/dividerHorizontal" />
+</LinearLayout>
diff --git a/res/layout/stream_item_pair.xml b/res/layout/stream_item_pair.xml
new file mode 100644
index 0000000..61b248c
--- /dev/null
+++ b/res/layout/stream_item_pair.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <FrameLayout android:id="@+id/stream_pair_first"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1" />
+
+    <FrameLayout android:id="@+id/stream_pair_second"
+        android:paddingLeft="@dimen/detail_update_section_internal_padding"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/stream_item_text.xml b/res/layout/stream_item_text.xml
new file mode 100644
index 0000000..601dfb9
--- /dev/null
+++ b/res/layout/stream_item_text.xml
@@ -0,0 +1,33 @@
+<?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:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView android:id="@+id/stream_item_html"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="16sp"
+        android:textColor="@color/social_update_text_color" />
+
+    <TextView android:id="@+id/stream_item_attribution"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="@color/social_update_attribution_color"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/menu/call_details_options.xml b/res/menu/call_details_options.xml
new file mode 100644
index 0000000..68e265c
--- /dev/null
+++ b/res/menu/call_details_options.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/remove_from_call_log"
+        android:icon="@android:drawable/ic_menu_close_clear_cancel"
+        android:title="@string/recentCalls_removeFromRecentList"
+        android:showAsAction="withText"
+    />
+
+    <item
+        android:id="@+id/edit_number_before_call"
+        android:title="@string/recentCalls_editNumberBeforeCall"
+        android:showAsAction="withText"
+    />
+</menu>
diff --git a/res/menu/call_log_options.xml b/res/menu/call_log_options.xml
index 1f8de57..0d14f2f 100644
--- a/res/menu/call_log_options.xml
+++ b/res/menu/call_log_options.xml
@@ -21,6 +21,11 @@
         android:showAsAction="withText" />
 
     <item
+        android:id="@+id/show_voicemails_only"
+        android:title="@string/menu_show_voicemails_only"
+        android:showAsAction="withText" />
+
+    <item
         android:id="@+id/menu_call_settings_call_log"
         android:title="@string/call_settings"
         android:showAsAction="withText" />
diff --git a/res/values-sw580dp-w1000dp/dimens.xml b/res/values-sw580dp-w1000dp/dimens.xml
index dfa1b81..ac30b76 100644
--- a/res/values-sw580dp-w1000dp/dimens.xml
+++ b/res/values-sw580dp-w1000dp/dimens.xml
@@ -22,4 +22,7 @@
     <dimen name="detail_header_view_margin">16dip</dimen>
     <dimen name="detail_header_attribution_height">56dip</dimen>
     <dimen name="detail_tab_carousel_height">0dip</dimen>
+    <dimen name="detail_update_section_top_padding">48dip</dimen>
+    <dimen name="contact_browser_list_left_margin">0dip</dimen>
+    <dimen name="contacts_count_right_margin">24dip</dimen>
 </resources>
diff --git a/res/values-sw580dp-w720dp/dimens.xml b/res/values-sw580dp-w720dp/dimens.xml
index a8aedb8..c072a95 100644
--- a/res/values-sw580dp-w720dp/dimens.xml
+++ b/res/values-sw580dp-w720dp/dimens.xml
@@ -21,4 +21,6 @@
     <dimen name="editor_title_label_width">150dip</dimen>
     <dimen name="editor_interpolator_narrow_width">800dip</dimen>
     <dimen name="editor_name_text_field_right_margin">10dip</dimen>
+    <dimen name="contact_browser_list_left_margin">0dip</dimen>
+    <dimen name="contacts_count_right_margin">24dip</dimen>
 </resources>
\ No newline at end of file
diff --git a/res/values-sw580dp-w720dp/styles.xml b/res/values-sw580dp-w720dp/styles.xml
index fc2b3be..4efaa09 100644
--- a/res/values-sw580dp-w720dp/styles.xml
+++ b/res/values-sw580dp-w720dp/styles.xml
@@ -33,9 +33,11 @@
         <item name="list_item_photo_size">64dip</item>
         <item name="list_item_profile_photo_size">80dip</item>
         <item name="list_item_prefix_highlight_color">#729a27</item>
-        <item name="list_item_header_text_indent">77dip</item>
-        <item name="list_item_header_text_color">?color/section_header_text_color</item>
+        <item name="list_item_header_text_indent">8dip</item>
+        <item name="list_item_header_text_color">@color/people_app_theme_color</item>
+        <item name="list_item_header_height">26dip</item>
         <item name="list_item_header_text_size">14sp</item>
+        <item name="list_item_header_underline_color">@color/people_app_theme_color</item>
         <item name="contact_filter_popup_width">320dip</item>
     </style>
 
diff --git a/res/values-sw580dp/dimens.xml b/res/values-sw580dp/dimens.xml
index bcaf1d2..9fbdc8e 100644
--- a/res/values-sw580dp/dimens.xml
+++ b/res/values-sw580dp/dimens.xml
@@ -33,5 +33,8 @@
     <dimen name="list_section_height">37dip</dimen>
     <dimen name="directory_header_height">56dip</dimen>
     <dimen name="detail_tab_carousel_height">256dip</dimen>
+    <dimen name="detail_update_section_item_vertical_padding">32dip</dimen>
     <dimen name="search_view_width">400dip</dimen>
+    <dimen name="contact_browser_list_left_margin">0dip</dimen>
+    <dimen name="contacts_count_right_margin">24dip</dimen>
 </resources>
diff --git a/res/values-sw580dp/styles.xml b/res/values-sw580dp/styles.xml
index 7e928b7..2838c81 100644
--- a/res/values-sw580dp/styles.xml
+++ b/res/values-sw580dp/styles.xml
@@ -22,7 +22,7 @@
         <item name="section_header_background">@drawable/list_title_holo</item>
         <item name="list_item_divider">?android:attr/listDivider</item>
         <item name="list_item_padding_top">0dip</item>
-        <item name="list_item_padding_right">20dip</item>
+        <item name="list_item_padding_right">24dip</item>
         <item name="list_item_padding_bottom">0dip</item>
         <item name="list_item_padding_left">0dip</item>
         <item name="list_item_gap_between_image_and_text">8dip</item>
@@ -33,9 +33,11 @@
         <item name="list_item_photo_size">64dip</item>
         <item name="list_item_profile_photo_size">80dip</item>
         <item name="list_item_prefix_highlight_color">#729a27</item>
-        <item name="list_item_header_text_indent">77dip</item>
-        <item name="list_item_header_text_color">?color/section_header_text_color</item>
+        <item name="list_item_header_text_indent">8dip</item>
         <item name="list_item_header_text_size">14sp</item>
+        <item name="list_item_header_text_color">@color/people_app_theme_color</item>
+        <item name="list_item_header_height">26dip</item>
+        <item name="list_item_header_underline_color">@color/people_app_theme_color</item>
         <item name="contact_filter_popup_width">320dip</item>
     </style>
 
diff --git a/res/values-w470dp/strings.xml b/res/values-w470dp/strings.xml
new file mode 100644
index 0000000..8dc9c09
--- /dev/null
+++ b/res/values-w470dp/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2006 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="recent_updates">Updates</string>
+</resources>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 7f4e237..ad04e12 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -29,6 +29,12 @@
 
     <color name="translucent_search_background">#cc000000</color>
 
+    <color name="background_social_updates">#ffeeeeee</color>
+
+    <color name="social_update_text_color">#ff333333</color>
+
+    <color name="social_update_attribution_color">#ff777777</color>
+
     <!-- Color used for the letter in the A-Z section header -->
     <color name="section_header_text_color">#ff999999</color>
 
@@ -60,7 +66,6 @@
     <color name="call_log_missed_call_highlight_color">#FF0000</color>
 
     <!-- Color of the text describing an unconsumed voicemail. -->
-
     <color name="call_log_voicemail_highlight_color">#33b5e5</color>
 
     <!-- Colour background for the voicemail playback ui. -->
@@ -77,4 +82,8 @@
 
     <!-- Color of the theme of the People app -->
     <color name="people_app_theme_color">#33B5E5</color>
+
+    <!-- Colors in the contact browser list -->
+    <color name="contact_browser_list_bk_color">#EEEEEE</color>
+    <color name="contact_count_text_color">#777777</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c3b1fee..b79bf0f 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -104,6 +104,21 @@
     <!-- Left and right padding of the text within the update tab in the tab carousel -->
     <dimen name="detail_update_tab_side_padding">24dip</dimen>
 
+    <!-- Top padding of the update section in the contact detail card -->
+    <dimen name="detail_update_section_top_padding">8dip</dimen>
+
+    <!-- Left and right padding of the update section in the contact detail card -->
+    <dimen name="detail_update_section_side_padding">16dip</dimen>
+
+    <!-- Vertical padding above and below individual stream items -->
+    <dimen name="detail_update_section_item_vertical_padding">16dip</dimen>
+
+    <!-- Left-side padding for individual stream items -->
+    <dimen name="detail_update_section_item_left_padding">8dip</dimen>
+
+    <!-- Horizontal padding between content sections within a stream item -->
+    <dimen name="detail_update_section_internal_padding">16dip</dimen>
+
     <!-- Margin around the contact's photo on the contact card -->
     <dimen name="detail_contact_photo_margin">15dip</dimen>
 
@@ -191,4 +206,9 @@
 
     <!-- Width of search view in action bar.  Use 0dip for MATCH_PARENT -->
     <dimen name="search_view_width">0dip</dimen>
+
+    <!-- contact browser list margins -->
+    <dimen name="contact_browser_list_left_margin">24dip</dimen>
+    <dimen name="contact_browser_list_top_margin">8dip</dimen>
+    <dimen name="contacts_count_right_margin">40dip</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8a97c27..cf4fff8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -86,10 +86,10 @@
          creating a new group. This string represents the built in way to create the group. [CHAR LIMIT=NONE] -->
     <string name="insertGroupDescription">Create group</string>
 
-    <!-- The tab label for the contact detail activity that displays information about the contact [CHAR LIMIT=11] -->
+    <!-- The tab label for the contact detail activity that displays information about the contact [CHAR LIMIT=15] -->
     <string name="contactDetailAbout">About</string>
 
-    <!-- The tab label for the contact detail activity that displays information about the contact [CHAR LIMIT=11] -->
+    <!-- The tab label for the contact detail activity that displays information about the contact [CHAR LIMIT=15] -->
     <string name="contactDetailUpdates">Updates</string>
 
     <!-- Hint text in the search box when the user hits the Search key while in the contacts app -->
@@ -1647,4 +1647,7 @@
 
     <!-- The "file name" displayed for vCards received directly via NFC [CHAR LIMIT=16] -->
     <string name="nfc_vcard_file_name">Contact received over NFC</string>
+
+    <!-- Menu item used to show only voicemails in the call log. [CHAR LIMIT=30] -->
+    <string name="menu_show_voicemails_only">Show voicemails only</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 9a73373..e4b7525 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -21,21 +21,25 @@
         <item name="section_header_background">@drawable/list_title_holo</item>
         <item name="list_section_header_height">32dip</item>
         <item name="list_item_divider">@drawable/list_item_divider</item>
-        <item name="list_item_padding_top">4dip</item>
-        <item name="list_item_padding_right">11dip</item>
-        <item name="list_item_padding_bottom">4dip</item>
-        <item name="list_item_padding_left">4dip</item>
+        <item name="list_item_padding_top">0dip</item>
+        <item name="list_item_padding_right">0dip</item>
+        <item name="list_item_padding_bottom">0dip</item>
+        <item name="list_item_padding_left">24dip</item>
         <item name="list_item_gap_between_image_and_text">8dip</item>
         <item name="list_item_gap_between_label_and_data">5dip</item>
         <item name="list_item_call_button_padding">14dip</item>
         <item name="list_item_vertical_divider_margin">5dip</item>
-        <item name="list_item_presence_icon_margin">5dip</item>
+        <item name="list_item_presence_icon_margin">4dip</item>
+        <item name="list_item_presence_icon_size">16dip</item>
         <item name="list_item_photo_size">56dip</item>
         <item name="list_item_profile_photo_size">70dip</item>
         <item name="list_item_prefix_highlight_color">#729a27</item>
-        <item name="list_item_header_text_indent">56dip</item>
-        <item name="list_item_header_text_color">#ffcccccc</item>
-        <item name="list_item_header_text_size">14sp</item>
+        <item name="list_item_header_text_indent">4dip</item>
+        <item name="list_item_header_text_color">@color/people_app_theme_color</item>
+        <item name="list_item_header_text_size">12sp</item>
+        <item name="list_item_header_height">24dip</item>
+        <item name="list_item_header_underline_height">1px</item>
+        <item name="list_item_header_underline_color">@color/people_app_theme_color</item>
         <item name="contact_filter_popup_width">320dip</item>
         <!-- CallLogList -->
         <item name="call_log_list_contact_photo_size">64dip</item>
@@ -136,12 +140,16 @@
         <attr name="list_item_call_button_padding" format="dimension"/>
         <attr name="list_item_vertical_divider_margin" format="dimension"/>
         <attr name="list_item_presence_icon_margin" format="dimension"/>
+        <attr name="list_item_presence_icon_size" format="dimension"/>
         <attr name="list_item_photo_size" format="dimension"/>
         <attr name="list_item_profile_photo_size" format="dimension"/>
         <attr name="list_item_prefix_highlight_color" format="color"/>
         <attr name="list_item_header_text_indent" format="dimension" />
         <attr name="list_item_header_text_color" format="color" />
         <attr name="list_item_header_text_size" format="dimension" />
+        <attr name="list_item_header_height" format="dimension" />
+        <attr name="list_item_header_underline_height" format="dimension" />
+        <attr name="list_item_header_underline_color" format="color" />
     </declare-styleable>
 
     <declare-styleable name="CallDetailActivity">
@@ -179,23 +187,27 @@
         <item name="list_item_height">?android:attr/listPreferredItemHeight</item>
         <item name="activated_background">@drawable/list_item_activated_background</item>
         <item name="section_header_background">@drawable/list_title_holo</item>
-        <item name="list_section_header_height">32dip</item>
+        <item name="list_section_header_height">24dip</item>
         <item name="list_item_divider">@drawable/list_item_divider</item>
-        <item name="list_item_padding_top">4dip</item>
-        <item name="list_item_padding_right">11dip</item>
-        <item name="list_item_padding_bottom">4dip</item>
-        <item name="list_item_padding_left">4dip</item>
+        <item name="list_item_padding_top">0dip</item>
+        <item name="list_item_padding_right">0dip</item>
+        <item name="list_item_padding_bottom">0dip</item>
+        <item name="list_item_padding_left">24dip</item>
         <item name="list_item_gap_between_image_and_text">8dip</item>
         <item name="list_item_gap_between_label_and_data">5dip</item>
         <item name="list_item_call_button_padding">14dip</item>
         <item name="list_item_vertical_divider_margin">5dip</item>
-        <item name="list_item_presence_icon_margin">5dip</item>
+        <item name="list_item_presence_icon_margin">4dip</item>
+        <item name="list_item_presence_icon_size">16dip</item>
         <item name="list_item_photo_size">56dip</item>
         <item name="list_item_profile_photo_size">70dip</item>
         <item name="list_item_prefix_highlight_color">#729a27</item>
-        <item name="list_item_header_text_indent">56dip</item>
-        <item name="list_item_header_text_color">?color/section_header_text_color</item>
-        <item name="list_item_header_text_size">14sp</item>
+        <item name="list_item_header_text_indent">4dip</item>
+        <item name="list_item_header_text_color">@color/people_app_theme_color</item>
+        <item name="list_item_header_text_size">12sp</item>
+        <item name="list_item_header_height">26dip</item>
+        <item name="list_item_header_underline_height">1px</item>
+        <item name="list_item_header_underline_color">@color/people_app_theme_color</item>
         <item name="contact_filter_popup_width">320dip</item>
     </style>
 
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 14fec85..68c9f61 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -45,6 +45,8 @@
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
@@ -99,6 +101,11 @@
     private TextView mStatusMessageText;
     private TextView mStatusMessageAction;
 
+    /** Whether we should show "remove from call log" in the options menu. */
+    private boolean mHasRemoveFromCallLog;
+    /** Whether we should show "edit number before call" in the options menu. */
+    private boolean mHasEditNumberBeforeCall;
+
     static final String[] CALL_LOG_PROJECTION = new String[] {
         CallLog.Calls.DATE,
         CallLog.Calls.DURATION,
@@ -169,7 +176,6 @@
         });
     }
 
-
     @Override
     public void onResume() {
         super.onResume();
@@ -184,24 +190,31 @@
      * playback.  If it doesn't, then hide the voicemail ui.
      */
     private void optionallyHandleVoicemail() {
-        Uri voicemailUri = getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI);
-        FragmentManager manager = getFragmentManager();
-        VoicemailPlaybackFragment fragment = (VoicemailPlaybackFragment) manager.findFragmentById(
-                R.id.voicemail_playback_fragment);
-        if (voicemailUri != null) {
-            // Has voicemail uri: leave the fragment visible.  Optionally start the playback.
+        if (hasVoicemail()) {
+            // Has voicemail: leave the fragment visible.  Optionally start the playback.
             // Do a query to fetch the voicemail status messages.
             boolean startPlayback = getIntent().getBooleanExtra(
                     EXTRA_VOICEMAIL_START_PLAYBACK, false);
-            fragment.setVoicemailUri(voicemailUri, startPlayback);
+            Uri voicemailUri = getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI);
+            getVoicemailPlaybackFragment().setVoicemailUri(voicemailUri, startPlayback);
             mAsyncQueryHandler.startVoicemailStatusQuery(voicemailUri);
         } else {
             // No voicemail uri: hide the voicemail fragment and the status view.
-            manager.beginTransaction().hide(fragment).commit();
+            getFragmentManager().beginTransaction().hide(getVoicemailPlaybackFragment()).commit();
             mStatusMessageView.setVisibility(View.GONE);
         }
     }
 
+    private boolean hasVoicemail() {
+        return getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI) != null;
+    }
+
+    private VoicemailPlaybackFragment getVoicemailPlaybackFragment() {
+        FragmentManager manager = getFragmentManager();
+        return (VoicemailPlaybackFragment) manager.findFragmentById(
+                R.id.voicemail_playback_fragment);
+    }
+
     /**
      * Returns the list of URIs to show.
      * <p>
@@ -349,35 +362,18 @@
         }
 
         // This action deletes all elements in the group from the call log.
-        actions.add(new ViewEntry(android.R.drawable.ic_menu_close_clear_cancel,
-                getString(R.string.recentCalls_removeFromRecentList),
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        StringBuilder callIds = new StringBuilder();
-                        for (Uri callUri : callUris) {
-                            if (callIds.length() != 0) {
-                                callIds.append(",");
-                            }
-                            callIds.append(ContentUris.parseId(callUri));
-                        }
+        // We don't have this action for voicemails, because you can just use the trash button.
+        mHasRemoveFromCallLog = !hasVoicemail();
+        mHasEditNumberBeforeCall = canPlaceCallsTo && !isSipNumber && !isVoicemailNumber;
 
-                        getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
-                                Calls._ID + " IN (" + callIds + ")", null);
-                        finish();
-                    }
-                }));
-
-        if (canPlaceCallsTo && !isSipNumber && !isVoicemailNumber) {
-            // "Edit the number before calling" is only available for PSTN numbers.
-            actions.add(new ViewEntry(android.R.drawable.sym_action_call,
-                    getString(R.string.recentCalls_editNumberBeforeCall),
-                    new Intent(Intent.ACTION_DIAL, numberCallUri)));
+        if (actions.size() != 0) {
+            // Set the actions for this phone number.
+            setListAdapter(new ViewAdapter(this, actions));
+            getListView().setVisibility(View.VISIBLE);
+        } else {
+            getListView().setVisibility(View.GONE);
         }
 
-        // Set the actions for this phone number.
-        setListAdapter(new ViewAdapter(this, actions));
-
         ListView historyList = (ListView) findViewById(R.id.history);
         historyList.setAdapter(
                 new CallDetailHistoryAdapter(this, mInflater, mCallTypeHelper, details));
@@ -622,4 +618,48 @@
         }
         return messages.get(0);
     }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.call_details_options, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        // This action deletes all elements in the group from the call log.
+        // We don't have this action for voicemails, because you can just use the trash button.
+        menu.findItem(R.id.remove_from_call_log).setVisible(mHasRemoveFromCallLog);
+        menu.findItem(R.id.edit_number_before_call).setVisible(mHasEditNumberBeforeCall);
+        return mHasRemoveFromCallLog || mHasEditNumberBeforeCall;
+    }
+
+    @Override
+    public boolean onMenuItemSelected(int featureId, MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.remove_from_call_log: {
+                StringBuilder callIds = new StringBuilder();
+                for (Uri callUri : getCallLogEntryUris()) {
+                    if (callIds.length() != 0) {
+                        callIds.append(",");
+                    }
+                    callIds.append(ContentUris.parseId(callUri));
+                }
+
+                getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
+                        Calls._ID + " IN (" + callIds + ")", null);
+                // Also close the activity.
+                finish();
+                return true;
+            }
+
+            case R.id.edit_number_before_call:
+                startActivity(
+                        new Intent(Intent.ACTION_DIAL, mPhoneNumberHelper.getCallUri(mNumber)));
+                return true;
+
+            default:
+                throw new IllegalArgumentException();
+        }
+    }
 }
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index f10e6b8..fa0ffb2 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -17,6 +17,8 @@
 package com.android.contacts;
 
 import com.android.contacts.util.DataStatus;
+import com.android.contacts.util.StreamItemEntry;
+import com.android.contacts.util.StreamItemPhotoEntry;
 import com.google.common.annotations.VisibleForTesting;
 
 import android.content.ContentResolver;
@@ -42,6 +44,8 @@
 import android.provider.ContactsContract.DisplayNameSources;
 import android.provider.ContactsContract.Groups;
 import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.StreamItems;
+import android.provider.ContactsContract.StreamItemPhotos;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -51,8 +55,12 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Loads a single Contact and all it constituent RawContacts.
@@ -62,6 +70,7 @@
 
     private Uri mLookupUri;
     private boolean mLoadGroupMetaData;
+    private boolean mLoadStreamItems;
     private Result mContact;
     private ForceLoadContentObserver mObserver;
     private boolean mDestroyed;
@@ -102,11 +111,8 @@
         private final boolean mStarred;
         private final Integer mPresence;
         private final ArrayList<Entity> mEntities;
+        private ArrayList<StreamItemEntry> mStreamItems;
         private final HashMap<Long, DataStatus> mStatuses;
-        private final String mStatus;
-        private final Long mStatusTimestamp;
-        private final Integer mStatusLabel;
-        private final String mStatusResPackage;
 
         private String mDirectoryDisplayName;
         private String mDirectoryType;
@@ -130,6 +136,7 @@
             mLookupKey = null;
             mId = -1;
             mEntities = null;
+            mStreamItems = new ArrayList<StreamItemEntry>();
             mStatuses = null;
             mNameRawContactId = -1;
             mDisplayNameSource = DisplayNameSources.UNDEFINED;
@@ -140,10 +147,6 @@
             mPhoneticName = null;
             mStarred = false;
             mPresence = null;
-            mStatus = null;
-            mStatusTimestamp = null;
-            mStatusLabel = null;
-            mStatusResPackage = null;
         }
 
         /**
@@ -152,14 +155,14 @@
         private Result(Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id,
                 long nameRawContactId, int displayNameSource, long photoId, String photoUri,
                 String displayName, String altDisplayName, String phoneticName, boolean starred,
-                Integer presence, String status, Long statusTimestamp, Integer statusLabel,
-                String statusResPackage) {
+                Integer presence) {
             mLookupUri = lookupUri;
             mUri = uri;
             mDirectoryId = directoryId;
             mLookupKey = lookupKey;
             mId = id;
             mEntities = new ArrayList<Entity>();
+            mStreamItems = new ArrayList<StreamItemEntry>();
             mStatuses = new HashMap<Long, DataStatus>();
             mNameRawContactId = nameRawContactId;
             mDisplayNameSource = displayNameSource;
@@ -170,10 +173,6 @@
             mPhoneticName = phoneticName;
             mStarred = starred;
             mPresence = presence;
-            mStatus = status;
-            mStatusTimestamp = statusTimestamp;
-            mStatusLabel = statusLabel;
-            mStatusResPackage = statusResPackage;
         }
 
         private Result(Result from) {
@@ -192,11 +191,8 @@
             mStarred = from.mStarred;
             mPresence = from.mPresence;
             mEntities = from.mEntities;
+            mStreamItems = from.mStreamItems;
             mStatuses = from.mStatuses;
-            mStatus = from.mStatus;
-            mStatusTimestamp = from.mStatusTimestamp;
-            mStatusLabel = from.mStatusLabel;
-            mStatusResPackage = from.mStatusResPackage;
 
             mDirectoryDisplayName = from.mDirectoryDisplayName;
             mDirectoryType = from.mDirectoryType;
@@ -283,26 +279,14 @@
             return mPresence;
         }
 
-        public String getSocialSnippet() {
-            return mStatus;
-        }
-
-        public Long getStatusTimestamp() {
-            return mStatusTimestamp;
-        }
-
-        public Integer getStatusLabel() {
-            return mStatusLabel;
-        }
-
-        public String getStatusResPackage() {
-            return mStatusResPackage;
-        }
-
         public ArrayList<Entity> getEntities() {
             return mEntities;
         }
 
+        public ArrayList<StreamItemEntry> getStreamItems() {
+            return mStreamItems;
+        }
+
         public HashMap<Long, DataStatus> getStatuses() {
             return mStatuses;
         }
@@ -387,8 +371,11 @@
         }
     }
 
+    /**
+     * Projection used for the query that loads all data for the entire contact (except for
+     * social stream items).
+     */
     private static class ContactQuery {
-        // Projection used for the query that loads all data for the entire contact.
         final static String[] COLUMNS = new String[] {
                 Contacts.NAME_RAW_CONTACT_ID,
                 Contacts.DISPLAY_NAME_SOURCE,
@@ -524,8 +511,10 @@
         public final static int PHOTO_URI = 59;
     }
 
+    /**
+     * Projection used for the query that loads all data for the entire contact.
+     */
     private static class DirectoryQuery {
-        // Projection used for the query that loads all data for the entire contact.
         final static String[] COLUMNS = new String[] {
             Directory.DISPLAY_NAME,
             Directory.PACKAGE_NAME,
@@ -575,6 +564,9 @@
                     } else if (mLoadGroupMetaData) {
                         loadGroupMetaData(result);
                     }
+                    if (mLoadStreamItems) {
+                        loadStreamItems(result);
+                    }
                     loadPhotoBinaryData(result);
                 }
                 return result;
@@ -748,15 +740,6 @@
             final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
                     ? null
                     : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
-            final String status = cursor.getString(ContactQuery.CONTACT_STATUS);
-            final Long statusTimestamp = cursor.isNull(ContactQuery.CONTACT_STATUS_TIMESTAMP)
-                    ? null
-                    : cursor.getLong(ContactQuery.CONTACT_STATUS_TIMESTAMP);
-            final Integer statusLabel = cursor.isNull(ContactQuery.CONTACT_STATUS_LABEL)
-                    ? null
-                    : cursor.getInt(ContactQuery.CONTACT_STATUS_LABEL);
-            final String statusResPackage = cursor.getString(
-                    ContactQuery.CONTACT_STATUS_RES_PACKAGE);
 
             Uri lookupUri;
             if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
@@ -768,8 +751,7 @@
 
             return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
                     nameRawContactId, displayNameSource, photoId, photoUri, displayName,
-                    altDisplayName, phoneticName, starred, presence, status, statusTimestamp,
-                    statusLabel, statusResPackage);
+                    altDisplayName, phoneticName, starred, presence);
         }
 
         /**
@@ -937,6 +919,60 @@
             }
         }
 
+        /**
+         * 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.
+            String[] streamItemIdArr = new String[streamItems.size()];
+            StringBuilder streamItemPhotoSelection = new StringBuilder();
+            if (!streamItems.isEmpty()) {
+                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 = getContext().getContentResolver().query(StreamItems.CONTENT_PHOTO_URI,
+                        null, streamItemPhotoSelection.toString(), streamItemIdArr,
+                        StreamItemPhotos.STREAM_ITEM_ID);
+                try {
+                    while (cursor.moveToNext()) {
+                        long streamItemId = cursor.getLong(
+                                cursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
+                        StreamItemEntry streamItem = streamItemsById.get(streamItemId);
+                        streamItem.addPhoto(new StreamItemPhotoEntry(cursor));
+                    }
+                } finally {
+                    cursor.close();
+                }
+            }
+
+            // Set the sorted stream items on the result.
+            Collections.sort(streamItems);
+            result.mStreamItems.addAll(streamItems);
+        }
+
         @Override
         protected void onPostExecute(Result result) {
             unregisterObserver();
@@ -1022,13 +1058,15 @@
     }
 
     public ContactLoader(Context context, Uri lookupUri) {
-        this(context, lookupUri, false);
+        this(context, lookupUri, false, false);
     }
 
-    public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData) {
+    public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
+            boolean loadStreamItems) {
         super(context);
         mLookupUri = lookupUri;
         mLoadGroupMetaData = loadGroupMetaData;
+        mLoadStreamItems = loadStreamItems;
     }
 
     public Uri getLookupUri() {
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 14378d9..4d04ac2 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -283,7 +283,7 @@
                 public void run() {
                     mContactData = result;
                     mLookupUri = result.getLookupUri();
-                    mContactHasUpdates = result.getSocialSnippet() != null;
+                    mContactHasUpdates = !result.getStreamItems().isEmpty();
                     invalidateOptionsMenu();
                     setupTitle();
                     if (mContactHasUpdates) {
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 3383ab2..f83938a 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -974,7 +974,7 @@
 
     private void startCallsQuery() {
         mAdapter.setLoading(true);
-        mCallLogQueryHandler.fetchCalls();
+        mCallLogQueryHandler.fetchAllCalls();
     }
 
     private void startVoicemailStatusQuery() {
@@ -990,6 +990,7 @@
     @Override
     public void onPrepareOptionsMenu(Menu menu) {
         menu.findItem(R.id.delete_all).setVisible(mShowOptionsMenu);
+        menu.findItem(R.id.show_voicemails_only).setVisible(mShowOptionsMenu);
         final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings_call_log);
         if (mShowOptionsMenu) {
             callSettingsMenuItem.setVisible(true);
@@ -1006,8 +1007,18 @@
                 ClearCallLogDialog.show(getFragmentManager());
                 return true;
             }
+
+            case R.id.show_voicemails_only: {
+                mCallLogQueryHandler.fetchVoicemailOnly();
+                return true;
+            }
+            case R.id.menu_call_settings_call_log: {
+                // Intent is already set.
+                return false;
+            }
+            default:
+                throw new IllegalArgumentException("unknown menu item: " + item.getItemId());
         }
-        return super.onOptionsItemSelected(item);
     }
 
     /*
diff --git a/src/com/android/contacts/calllog/CallLogQueryHandler.java b/src/com/android/contacts/calllog/CallLogQueryHandler.java
index 8569190..fdf55b9 100644
--- a/src/com/android/contacts/calllog/CallLogQueryHandler.java
+++ b/src/com/android/contacts/calllog/CallLogQueryHandler.java
@@ -121,42 +121,57 @@
      * <p>
      * It will asynchronously update the content of the list view when the fetch completes.
      */
-    public void fetchCalls() {
+    public void fetchAllCalls() {
         cancelFetch();
         invalidate();
-        fetchNewCalls();
-        fetchOldCalls();
+        fetchCalls(QUERY_NEW_CALLS_TOKEN, true /*isNew*/, false /*voicemailOnly*/);
+        fetchCalls(QUERY_OLD_CALLS_TOKEN, false /*isNew*/, false /*voicemailOnly*/);
     }
 
+    /**
+     * Fetches the list of calls from the call log but include only the voicemail.
+     * <p>
+     * It will asynchronously update the content of the list view when the fetch completes.
+     */
+    public void fetchVoicemailOnly() {
+        cancelFetch();
+        invalidate();
+        fetchCalls(QUERY_NEW_CALLS_TOKEN, true /*isNew*/, true /*voicemailOnly*/);
+        fetchCalls(QUERY_OLD_CALLS_TOKEN, false /*isNew*/, true /*voicemailOnly*/);
+    }
+
+
     public void fetchVoicemailStatus() {
         startQuery(QUERY_VOICEMAIL_STATUS_TOKEN, null, Status.CONTENT_URI,
                 VoicemailStatusHelperImpl.PROJECTION, null, null, null);
     }
 
-    /** Fetches the list of new calls in the call log. */
-    private void fetchNewCalls() {
-        fetchCalls(QUERY_NEW_CALLS_TOKEN, true);
-    }
-
-    /** Fetch the list of old calls in the call log. */
-    private void fetchOldCalls() {
-        fetchCalls(QUERY_OLD_CALLS_TOKEN, false);
-    }
-
     /** Fetches the list of calls in the call log, either the new one or the old ones. */
-    private void fetchCalls(int token, boolean isNew) {
+    private void fetchCalls(int token, boolean isNew, boolean voicemailOnly) {
         // We need to check for NULL explicitly otherwise entries with where NEW is NULL will not
         // match either the query or its negation.
         String selection =
                 String.format("%s IS NOT NULL AND %s = 1 AND (%s = ? OR %s = ?)",
                         Calls.NEW, Calls.NEW, Calls.TYPE, Calls.TYPE);
-        String[] selectionArgs = new String[]{
-                Integer.toString(Calls.MISSED_TYPE),
-                Integer.toString(Calls.VOICEMAIL_TYPE),
-        };
+        final String[] selectionArgs;
         if (!isNew) {
+            // Negate the query.
             selection = String.format("NOT (%s)", selection);
         }
+        if (voicemailOnly) {
+            // Add a clause to fetch only items of type voicemail.
+            selection = String.format("(%s) AND (%s = ?)", selection, Calls.TYPE);
+            selectionArgs = new String[]{
+                    Integer.toString(Calls.MISSED_TYPE),
+                    Integer.toString(Calls.VOICEMAIL_TYPE),
+                    Integer.toString(Calls.VOICEMAIL_TYPE),
+            };
+        } else {
+            selectionArgs = new String[]{
+                    Integer.toString(Calls.MISSED_TYPE),
+                    Integer.toString(Calls.VOICEMAIL_TYPE),
+            };
+        }
         startQuery(token, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
                 CallLogQuery._PROJECTION, selection, selectionArgs, Calls.DEFAULT_SORT_ORDER);
     }
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index 3f28b58..e9d75ef 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -18,10 +18,13 @@
 
 import com.android.contacts.ContactLoader;
 import com.android.contacts.ContactLoader.Result;
+import com.android.contacts.ContactPhotoManager;
 import com.android.contacts.R;
 import com.android.contacts.format.FormatUtils;
 import com.android.contacts.preference.ContactsPreferences;
 import com.android.contacts.util.ContactBadgeUtil;
+import com.android.contacts.util.StreamItemEntry;
+import com.android.contacts.util.StreamItemPhotoEntry;
 
 import android.content.ContentValues;
 import android.content.Context;
@@ -30,19 +33,26 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Typeface;
+import android.net.Uri;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.DisplayNameSources;
+import android.text.Html;
 import android.text.Spanned;
 import android.text.TextUtils;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.AlphaAnimation;
 import android.widget.CheckBox;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import java.util.List;
+
 /**
  * This class contains utility methods to bind high-level contact details
  * (meaning name, phonetic name, job, and attribution) from a
@@ -207,23 +217,135 @@
     /**
      * Set the social snippet text. If there isn't one, then set the view to gone.
      */
-    public static void setSocialSnippet(Context context, Result contactData, TextView statusView) {
+    public static void setSocialSnippet(Context context, Result contactData, TextView statusView,
+            ImageView statusPhotoView) {
         if (statusView == null) {
             return;
         }
-        setDataOrHideIfNone(contactData.getSocialSnippet(), statusView);
+
+        String snippet = null;
+        String photoUri = null;
+        if (!contactData.getStreamItems().isEmpty()) {
+            StreamItemEntry firstEntry = contactData.getStreamItems().get(0);
+            snippet = firstEntry.getText();
+            if (!firstEntry.getPhotos().isEmpty()) {
+                StreamItemPhotoEntry firstPhoto = firstEntry.getPhotos().get(0);
+                photoUri = firstPhoto.getPhotoUri();
+
+                // If displaying an image, hide the snippet text.
+                snippet = null;
+            }
+        }
+        setDataOrHideIfNone(snippet, statusView);
+        if (photoUri != null) {
+            ContactPhotoManager.getInstance(context).loadPhoto(
+                    statusPhotoView, Uri.parse(photoUri));
+            statusPhotoView.setVisibility(View.VISIBLE);
+        } else {
+            statusPhotoView.setVisibility(View.GONE);
+        }
     }
 
     /**
-     * Set the social snippet text and date. If there isn't one, then set the view to gone.
+     * Displays the social stream items under the given layout.
      */
-    public static void setSocialSnippetAndDate(Context context, Result contactData,
-            TextView statusView, TextView dateView) {
-        if (statusView == null || dateView == null) {
-            return;
+    public static void showSocialStreamItems(LayoutInflater inflater, Context context,
+            Result contactData, LinearLayout streamContainer) {
+        if (streamContainer != null) {
+            streamContainer.removeAllViews();
+            List<StreamItemEntry> streamItems = contactData.getStreamItems();
+            for (StreamItemEntry streamItem : streamItems) {
+                addStreamItemToContainer(inflater, context, streamItem, streamContainer);
+            }
         }
-        setDataOrHideIfNone(contactData.getSocialSnippet(), statusView);
-        setDataOrHideIfNone(ContactBadgeUtil.getSocialDate(contactData, context), dateView);
+    }
+
+    public static void addStreamItemToContainer(LayoutInflater inflater, Context context,
+            StreamItemEntry streamItem, LinearLayout streamContainer) {
+        View oneColumnView = inflater.inflate(R.layout.stream_item_one_column,
+                streamContainer, false);
+        ViewGroup contentBox = (ViewGroup) oneColumnView.findViewById(R.id.stream_item_content);
+        int internalPadding = context.getResources().getDimensionPixelSize(
+                R.dimen.detail_update_section_internal_padding);
+
+        // TODO: This is not the correct layout for a stream item with photos.  Photos should be
+        // displayed first, then the update text either to the right of the final image (if there
+        // are an odd number of images) or below the last row of images (if there are an even
+        // number of images).  Since this is designed as a two-column grid, we should also consider
+        // using a TableLayout instead of the series of nested LinearLayouts that we have now.
+        // See the Updates section of the Contacts Architecture document for details.
+
+        // If there are no photos, just display the text in a single column.
+        List<StreamItemPhotoEntry> photos = streamItem.getPhotos();
+        if (photos.isEmpty()) {
+            addStreamItemText(inflater, context, streamItem, contentBox);
+        } else {
+            // If the first photo is square or portrait mode, show the text alongside it.
+            boolean isFirstPhotoAlongsideText = false;
+            StreamItemPhotoEntry firstPhoto = photos.get(0);
+            isFirstPhotoAlongsideText = firstPhoto.getHeight() >= firstPhoto.getWidth();
+            if (isFirstPhotoAlongsideText) {
+                View twoColumnView = inflater.inflate(R.layout.stream_item_pair, contentBox, false);
+                addStreamItemPhoto(inflater, context, firstPhoto,
+                        (ViewGroup) twoColumnView.findViewById(R.id.stream_pair_first));
+                addStreamItemText(inflater, context, streamItem,
+                        (ViewGroup) twoColumnView.findViewById(R.id.stream_pair_second));
+                contentBox.addView(twoColumnView);
+            } else {
+                // Just add the stream item text at the top of the entry.
+                addStreamItemText(inflater, context, streamItem, contentBox);
+            }
+            for (int i = isFirstPhotoAlongsideText ? 1 : 0; i < photos.size(); i++) {
+                StreamItemPhotoEntry photo = photos.get(i);
+
+                // If the photo is landscape, show it at full-width.
+                if (photo.getWidth() > photo.getHeight()) {
+                    View photoView = addStreamItemPhoto(inflater, context, photo, contentBox);
+                    photoView.setPadding(0, internalPadding, 0, 0);
+                } else {
+                    // If this photo and the next are both square or portrait, show them as a pair.
+                    StreamItemPhotoEntry nextPhoto = i + 1 < photos.size()
+                            ? photos.get(i + 1) : null;
+                    if (nextPhoto != null && nextPhoto.getHeight() >= nextPhoto.getWidth()) {
+                        View twoColumnView = inflater.inflate(R.layout.stream_item_pair,
+                                contentBox, false);
+                        addStreamItemPhoto(inflater, context, photo,
+                                (ViewGroup) twoColumnView.findViewById(R.id.stream_pair_first));
+                        addStreamItemPhoto(inflater, context, nextPhoto,
+                                (ViewGroup) twoColumnView.findViewById(R.id.stream_pair_second));
+                        twoColumnView.setPadding(0, internalPadding, 0, 0);
+                        contentBox.addView(twoColumnView);
+                        i++;
+                    } else {
+                        View photoView = addStreamItemPhoto(inflater, context, photo, contentBox);
+                        photoView.setPadding(0, internalPadding, 0, 0);
+                    }
+                }
+            }
+        }
+
+        streamContainer.addView(oneColumnView);
+    }
+
+    private static View addStreamItemText(LayoutInflater inflater, Context context,
+            StreamItemEntry streamItem, ViewGroup parent) {
+        View textUpdate = inflater.inflate(R.layout.stream_item_text, parent, false);
+        TextView htmlView = (TextView) textUpdate.findViewById(R.id.stream_item_html);
+        TextView attributionView = (TextView) textUpdate.findViewById(
+                R.id.stream_item_attribution);
+        htmlView.setText(Html.fromHtml(streamItem.getText()));
+        attributionView.setText(ContactBadgeUtil.getSocialDate(streamItem, context));
+        parent.addView(textUpdate);
+        return textUpdate;
+    }
+
+    private static View addStreamItemPhoto(LayoutInflater inflater, Context context,
+            StreamItemPhotoEntry streamItemPhoto, ViewGroup parent) {
+        ImageView image = new ImageView(context);
+        ContactPhotoManager.getInstance(context).loadPhoto(
+                image, Uri.parse(streamItemPhoto.getPhotoUri()));
+        parent.addView(image);
+        return image;
     }
 
     /**
diff --git a/src/com/android/contacts/detail/ContactDetailHeaderView.java b/src/com/android/contacts/detail/ContactDetailHeaderView.java
deleted file mode 100644
index 63f8fbe..0000000
--- a/src/com/android/contacts/detail/ContactDetailHeaderView.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.contacts.detail;
-
-import com.android.contacts.ContactLoader;
-import com.android.contacts.ContactLoader.Result;
-import com.android.contacts.ContactSaveService;
-import com.android.contacts.R;
-import com.android.contacts.format.FormatUtils;
-import com.android.contacts.preference.ContactsPreferences;
-import com.android.contacts.util.ContactBadgeUtil;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Entity;
-import android.content.Entity.NamedContentValues;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.AlphaAnimation;
-import android.widget.CheckBox;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-/**
- * Header for displaying a title bar with contact info. You
- * can bind specific values by calling
- * {@link ContactDetailHeaderView#loadData(com.android.contacts.ContactLoader.Result)}
- * TODO: Refactor to use {@link ContactDetailDisplayUtils}
- */
-public class ContactDetailHeaderView extends FrameLayout
-        implements View.OnClickListener, View.OnLongClickListener {
-    private static final String TAG = "ContactDetailHeaderView";
-
-    private static final int PHOTO_FADE_IN_ANIMATION_DURATION_MILLIS = 100;
-
-    private TextView mDisplayNameView;
-    private TextView mPhoneticNameView;
-    private TextView mOrganizationTextView;
-    private CheckBox mStarredView;
-    private ImageView mPhotoView;
-    private View mStatusContainerView;
-    private TextView mStatusView;
-    private TextView mStatusDateView;
-    private TextView mAttributionView;
-
-    private Uri mContactUri;
-    private Listener mListener;
-
-    /**
-     * Interface for callbacks invoked when the user interacts with a header.
-     */
-    public interface Listener {
-        public void onPhotoClick(View view);
-        public void onDisplayNameClick(View view);
-    }
-
-    public ContactDetailHeaderView(Context context) {
-        this(context, null);
-    }
-
-    public ContactDetailHeaderView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public ContactDetailHeaderView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        final LayoutInflater inflater =
-            (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        inflater.inflate(R.layout.contact_detail_header_view, this);
-
-        mDisplayNameView = (TextView) findViewById(R.id.name);
-        mDisplayNameView.setOnLongClickListener(this);
-
-        mPhoneticNameView = (TextView) findViewById(R.id.phonetic_name);
-        mPhoneticNameView.setOnLongClickListener(this);
-
-        mOrganizationTextView = (TextView) findViewById(R.id.organization);
-        mOrganizationTextView.setOnLongClickListener(this);
-
-        mStarredView = (CheckBox)findViewById(R.id.star);
-        mStarredView.setOnClickListener(this);
-
-        mPhotoView = (ImageView) findViewById(R.id.photo);
-
-        mStatusContainerView = findViewById(R.id.status_container);
-        mStatusView = (TextView)findViewById(R.id.status);
-        mStatusDateView = (TextView)findViewById(R.id.status_date);
-
-        mAttributionView = (TextView) findViewById(R.id.attribution);
-    }
-
-    /**
-     * Loads the data from the Loader-Result. This is the only function that has to be called
-     * from the outside to fully setup the View
-     */
-    public void loadData(ContactLoader.Result contactData) {
-        mContactUri = contactData.getLookupUri();
-
-        setDisplayName(contactData.getDisplayName(), contactData.getAltDisplayName(),
-                contactData.getPhoneticName());
-        setCompany(contactData);
-        if (contactData.isLoadingPhoto()) {
-            setPhoto(null, false);
-        } else {
-            byte[] photo = contactData.getPhotoBinaryData();
-            setPhoto(photo != null ? BitmapFactory.decodeByteArray(photo, 0, photo.length)
-                            : ContactBadgeUtil.loadPlaceholderPhoto(mContext),
-                    contactData.isDirectoryEntry());
-        }
-
-        setStared(!contactData.isDirectoryEntry(), contactData.getStarred());
-        setSocialSnippet(contactData.getSocialSnippet());
-        setSocialDate(ContactBadgeUtil.getSocialDate(contactData, getContext()));
-        setAttribution(contactData.getEntities().size() > 1, contactData.isDirectoryEntry(),
-                contactData.getDirectoryDisplayName(), contactData.getDirectoryType());
-    }
-
-    /**
-     * Set the given {@link Listener} to handle header events.
-     */
-    public void setListener(Listener listener) {
-        mListener = listener;
-    }
-
-    private void performPhotoClick() {
-        if (mListener != null) {
-            mListener.onPhotoClick(mPhotoView);
-        }
-    }
-
-    private void performDisplayNameClick() {
-        if (mListener != null) {
-            mListener.onDisplayNameClick(mDisplayNameView);
-        }
-    }
-
-    /**
-     * Set the starred state of this header widget.
-     */
-    private void setStared(boolean visible, boolean starred) {
-        if (visible) {
-            mStarredView.setVisibility(View.VISIBLE);
-            mStarredView.setChecked(starred);
-        } else {
-            mStarredView.setVisibility(View.GONE);
-        }
-    }
-
-    /**
-     * Set the photo to display in the header. If bitmap is null, the default placeholder
-     * image is shown
-     */
-    private void setPhoto(Bitmap bitmap, boolean fadeIn) {
-        if (mPhotoView.getDrawable() == null && fadeIn) {
-            AlphaAnimation animation = new AlphaAnimation(0, 1);
-            animation.setDuration(PHOTO_FADE_IN_ANIMATION_DURATION_MILLIS);
-            animation.setInterpolator(new AccelerateInterpolator());
-            mPhotoView.startAnimation(animation);
-        }
-        mPhotoView.setImageBitmap(bitmap);
-    }
-
-    /**
-     * Set the display name and phonetic name to show in the header.
-     */
-    private void setDisplayName(CharSequence displayName, CharSequence altDisplayName,
-            CharSequence phoneticName) {
-
-        // Check the preference for display name ordering, and bold the contact's first name if
-        // possible.
-        ContactsPreferences prefs = new ContactsPreferences(getContext());
-        CharSequence styledName = "";
-        if (!TextUtils.isEmpty(displayName) && !TextUtils.isEmpty(altDisplayName)) {
-            if (prefs.getDisplayOrder() == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
-                int overlapPoint = FormatUtils.overlapPoint(
-                        displayName.toString(), altDisplayName.toString());
-                if (overlapPoint > 0) {
-                    styledName = FormatUtils.applyStyleToSpan(Typeface.BOLD,
-                            displayName, 0, overlapPoint, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {
-                    styledName = displayName;
-                }
-            } else {
-                // Displaying alternate display name.
-                int overlapPoint = FormatUtils.overlapPoint(
-                        altDisplayName.toString(), displayName.toString());
-                if (overlapPoint > 0) {
-                    styledName = FormatUtils.applyStyleToSpan(Typeface.BOLD,
-                            altDisplayName, overlapPoint, altDisplayName.length(),
-                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                } else {
-                    styledName = altDisplayName;
-                }
-            }
-        }
-        mDisplayNameView.setText(styledName);
-
-        if (TextUtils.isEmpty(phoneticName)) {
-            mPhoneticNameView.setVisibility(View.GONE);
-        } else {
-            mPhoneticNameView.setText(phoneticName);
-            mPhoneticNameView.setVisibility(View.VISIBLE);
-        }
-    }
-
-    /**
-     * Sets the organization info. If several organizations are given, the first one is used
-     */
-    private void setCompany(Result contactData) {
-        final boolean displayNameIsOrganization =
-            contactData.getDisplayNameSource() == DisplayNameSources.ORGANIZATION;
-        for (Entity entity : contactData.getEntities()) {
-            for (NamedContentValues subValue : entity.getSubValues()) {
-                final ContentValues entryValues = subValue.values;
-                final String mimeType = entryValues.getAsString(Data.MIMETYPE);
-
-                if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final String company = entryValues.getAsString(Organization.COMPANY);
-                    final String title = entryValues.getAsString(Organization.TITLE);
-                    final String combined;
-                    // We need to show company and title in a combined string. However, if the
-                    // DisplayName is already the organization, it mirrors company or (if company
-                    // is empty title). Make sure we don't show what's already shown as DisplayName
-                    if (TextUtils.isEmpty(company)) {
-                        combined = displayNameIsOrganization ? null : title;
-                    } else {
-                        if (TextUtils.isEmpty(title)) {
-                            combined = displayNameIsOrganization ? null : company;
-                        } else {
-                            if (displayNameIsOrganization) {
-                                combined = title;
-                            } else {
-                                combined = getResources().getString(
-                                        R.string.organization_company_and_title,
-                                        company, title);
-                            }
-                        }
-                    }
-
-                    if (TextUtils.isEmpty(combined)) {
-                        mOrganizationTextView.setVisibility(GONE);
-                    } else {
-                        mOrganizationTextView.setVisibility(VISIBLE);
-                        mOrganizationTextView.setText(combined);
-                    }
-
-                    return;
-                }
-            }
-        }
-        mOrganizationTextView.setVisibility(GONE);
-    }
-
-    /**
-     * Set the social snippet text to display in the header.
-     */
-    private void setSocialSnippet(CharSequence snippet) {
-        if (TextUtils.isEmpty(snippet)) {
-            // No status info. Hide everything
-            if (mStatusContainerView != null) mStatusContainerView.setVisibility(View.GONE);
-            mStatusView.setVisibility(View.GONE);
-            mStatusDateView.setVisibility(View.GONE);
-        } else {
-            // We have status info. Show the bubble
-            if (mStatusContainerView != null) mStatusContainerView.setVisibility(View.VISIBLE);
-            mStatusView.setVisibility(View.VISIBLE);
-            mStatusView.setText(snippet);
-        }
-    }
-
-    /**
-     * Set the status attribution text to display in the header.
-     */
-
-    private void setSocialDate(CharSequence dateText) {
-        if (TextUtils.isEmpty(dateText)) {
-            mStatusDateView.setVisibility(View.GONE);
-        } else {
-            mStatusDateView.setText(dateText);
-            mStatusDateView.setVisibility(View.VISIBLE);
-        }
-    }
-
-    private void setAttribution(boolean isJoinedContact, boolean isDirectoryEntry,
-            String directoryDisplayName, String directoryType) {
-        if (isJoinedContact) {
-            mAttributionView.setText(R.string.indicator_joined_contact);
-            mAttributionView.setVisibility(View.VISIBLE);
-        } else if (isDirectoryEntry) {
-            String displayName = !TextUtils.isEmpty(directoryDisplayName)
-                    ? directoryDisplayName
-                    : directoryType;
-            String text = getContext().getString(
-                    R.string.contact_directory_description, displayName);
-            mAttributionView.setText(text);
-            mAttributionView.setVisibility(View.VISIBLE);
-        } else {
-            mAttributionView.setVisibility(View.INVISIBLE);
-        }
-    }
-
-    @Override
-    public void onClick(View view) {
-        switch (view.getId()) {
-            case R.id.star: {
-                // Toggle "starred" state
-                // Make sure there is a contact
-                if (mContactUri != null) {
-                    Intent intent = ContactSaveService.createSetStarredIntent(
-                            getContext(), mContactUri, mStarredView.isChecked());
-                    getContext().startService(intent);
-                }
-                break;
-            }
-            case R.id.photo: {
-                performPhotoClick();
-                break;
-            }
-            case R.id.name: {
-                performDisplayNameClick();
-                break;
-            }
-        }
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        if (!(v instanceof TextView)) {
-            return false;
-        }
-
-        CharSequence text = ((TextView)v).getText();
-
-        if (TextUtils.isEmpty(text)) {
-            return false;
-        }
-
-        ClipboardManager cm = (ClipboardManager) getContext().getSystemService(
-                Context.CLIPBOARD_SERVICE);
-        cm.setPrimaryClip(ClipData.newPlainText(null, text));
-        Toast.makeText(getContext(), R.string.toast_text_copied, Toast.LENGTH_SHORT).show();
-        return true;
-    }
-}
diff --git a/src/com/android/contacts/detail/ContactDetailLayoutController.java b/src/com/android/contacts/detail/ContactDetailLayoutController.java
index dda3884..d93edea 100644
--- a/src/com/android/contacts/detail/ContactDetailLayoutController.java
+++ b/src/com/android/contacts/detail/ContactDetailLayoutController.java
@@ -18,6 +18,7 @@
 
 import com.android.contacts.ContactLoader;
 import com.android.contacts.activities.PeopleActivity.ContactDetailFragmentListener;
+import com.android.contacts.util.StreamItemEntry;
 
 import android.app.Fragment;
 import android.app.FragmentManager;
@@ -124,7 +125,7 @@
 
     public void setContactData(ContactLoader.Result data) {
         mContactData = data;
-        if (mContactData.getSocialSnippet() != null) {
+        if (!data.getStreamItems().isEmpty()) {
             showContactWithUpdates();
         } else {
             showContactWithoutUpdates();
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index 6e1b199..26987f6 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -45,6 +45,7 @@
 
     private ImageView mPhotoView;
     private TextView mStatusView;
+    private ImageView mStatusPhotoView;
 
     private Listener mListener;
 
@@ -115,6 +116,7 @@
 
         // Retrieve the social update views for the "updates" tab
         mStatusView = (TextView) updateView.findViewById(R.id.status);
+        mStatusPhotoView = (ImageView) updateView.findViewById(R.id.status_photo);
     }
 
     @Override
@@ -196,7 +198,8 @@
         }
 
         ContactDetailDisplayUtils.setPhoto(mContext, contactData, mPhotoView);
-        ContactDetailDisplayUtils.setSocialSnippet(mContext, contactData, mStatusView);
+        ContactDetailDisplayUtils.setSocialSnippet(mContext, contactData, mStatusView,
+                mStatusPhotoView);
     }
 
     /**
diff --git a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
index 85dcc1d..daeae00 100644
--- a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
@@ -27,6 +27,7 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 public class ContactDetailUpdatesFragment extends Fragment
@@ -37,8 +38,10 @@
     private ContactLoader.Result mContactData;
     private Uri mLookupUri;
 
-    private TextView mStatusView;
-    private TextView mStatusDateView;
+    private LayoutInflater mInflater;
+
+    // The linear layout that contains all the stream items.
+    private LinearLayout mStreamContainer;
 
     /**
      * This optional view adds an alpha layer over the entire fragment.
@@ -57,22 +60,22 @@
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
-        View rootView = inflater.inflate(R.layout.contact_detail_updates_fragment, container,
+        mInflater = inflater;
+        View rootView = mInflater.inflate(R.layout.contact_detail_updates_fragment, container,
                 false);
 
         TextView titleTextView = (TextView) rootView.findViewById(R.id.kind);
         titleTextView.setText(getString(R.string.recent_updates).toUpperCase());
 
-        mStatusView = (TextView) rootView.findViewById(R.id.status);
-        mStatusDateView = (TextView) rootView.findViewById(R.id.status_date);
+        mStreamContainer = (LinearLayout) rootView.findViewById(R.id.update_list);
 
         // It is possible that the contact data was set to the fragment when it was first attached
         // to the activity, but before this method was called because the fragment was not
         // visible on screen yet (i.e. using a {@link ViewPager}), so display the data if we already
         // have it.
         if (mContactData != null) {
-            ContactDetailDisplayUtils.setSocialSnippetAndDate(getActivity(), mContactData,
-                    mStatusView, mStatusDateView);
+            ContactDetailDisplayUtils.showSocialStreamItems(inflater, getActivity(), mContactData,
+                    mStreamContainer);
         }
 
         mAlphaLayer = rootView.findViewById(R.id.alpha_overlay);
@@ -87,8 +90,8 @@
         }
         mLookupUri = lookupUri;
         mContactData = result;
-        ContactDetailDisplayUtils.setSocialSnippetAndDate(getActivity(), mContactData,
-                mStatusView, mStatusDateView);
+        ContactDetailDisplayUtils.showSocialStreamItems(mInflater, getActivity(), mContactData,
+                mStreamContainer);
     }
 
     @Override
diff --git a/src/com/android/contacts/detail/ContactLoaderFragment.java b/src/com/android/contacts/detail/ContactLoaderFragment.java
index 0dc83ef..034a8cc 100644
--- a/src/com/android/contacts/detail/ContactLoaderFragment.java
+++ b/src/com/android/contacts/detail/ContactLoaderFragment.java
@@ -170,7 +170,8 @@
         @Override
         public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
             Uri lookupUri = args.getParcelable(LOADER_ARG_CONTACT_URI);
-            return new ContactLoader(mContext, lookupUri, true /* loadGroupMetaData */);
+            return new ContactLoader(mContext, lookupUri, true /* loadGroupMetaData */,
+                    true /* loadStreamItems */);
         }
 
         @Override
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index df862a1..4fb6f1d 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -46,16 +46,28 @@
 
 /**
  * A custom view for an item in the contact list.
+ * The view contains the contact's photo, a set of text views (for name, status, etc...) and
+ * icons for presence and call.
+ * The view uses no XML file for layout and all the measurements and layouts are done
+ * in the onMeasure and onLayout methods.
+ *
+ * The layout puts the contact's photo on the right side of the view, the call icon (if present)
+ * to the left of the photo, the text lines are aligned to the left and the presence icon (if
+ * present) is set to the left of the status line.
+ *
+ * The layout also supports a header (used as a header of a group of contacts) that is above the
+ * contact's data and a divider between contact view.
  */
+
 public class ContactListItemView extends ViewGroup
-        implements SelectionBoundsAdjuster
-{
+        implements SelectionBoundsAdjuster {
 
     private static final int QUICK_CONTACT_BADGE_STYLE =
             com.android.internal.R.attr.quickContactBadgeStyleWindowMedium;
 
     protected final Context mContext;
 
+    // Style values for layout and appearance
     private final int mPreferredHeight;
     private final int mVerticalDividerMargin;
     private final int mPaddingTop;
@@ -66,25 +78,32 @@
     private final int mGapBetweenLabelAndData;
     private final int mCallButtonPadding;
     private final int mPresenceIconMargin;
+    private final int mPresenceIconSize;
     private final int mHeaderTextColor;
     private final int mHeaderTextIndent;
     private final int mHeaderTextSize;
+    private final int mHeaderUnderlineHeight;
+    private final int mHeaderUnderlineColor;
 
     private Drawable mActivatedBackgroundDrawable;
 
+    // Horizontal divider between contact views.
     private boolean mHorizontalDividerVisible = true;
     private Drawable mHorizontalDividerDrawable;
     private int mHorizontalDividerHeight;
 
+    // Vertical divider between the call icon and the text.
     private boolean mVerticalDividerVisible;
     private Drawable mVerticalDividerDrawable;
     private int mVerticalDividerWidth;
 
+    // Header layout data
     private boolean mHeaderVisible;
-    private Drawable mHeaderBackgroundDrawable;
+    private View mHeaderDivider;
     private int mHeaderBackgroundHeight;
     private TextView mHeaderTextView;
 
+    // The views inside the contact view
     private boolean mQuickContactEnabled = true;
     private QuickContactBadge mQuickContact;
     private ImageView mPhotoView;
@@ -94,6 +113,7 @@
     private TextView mLabelView;
     private TextView mDataView;
     private TextView mSnippetView;
+    private TextView mStatusView;
     private ImageView mPresenceIcon;
 
     private char[] mHighlightedPrefix;
@@ -126,10 +146,11 @@
      */
     private boolean mPhotoViewWidthAndHeightAreReady = false;
 
-    private int mLine1Height;
-    private int mLine2Height;
-    private int mLine3Height;
-    private int mLine4Height;
+    private int mNameTextViewHeight;
+    private int mPhoneticNameTextViewHeight;
+    private int mLabelTextViewHeight;
+    private int mSnippetTextViewHeight;
+    private int mStatusTextViewHeight;
 
     private OnClickListener mCallButtonClickListener;
     private CharArrayBuffer mDataBuffer = new CharArrayBuffer(128);
@@ -169,13 +190,12 @@
         super(context, attrs);
         mContext = context;
 
+        // Read all style values
         TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ContactListItemView);
         mPreferredHeight = a.getDimensionPixelSize(
                 R.styleable.ContactListItemView_list_item_height, 0);
         mActivatedBackgroundDrawable = a.getDrawable(
                 R.styleable.ContactListItemView_activated_background);
-        mHeaderBackgroundDrawable = a.getDrawable(
-                R.styleable.ContactListItemView_section_header_background);
         mHorizontalDividerDrawable = a.getDrawable(
                 R.styleable.ContactListItemView_list_item_divider);
         mVerticalDividerMargin = a.getDimensionPixelOffset(
@@ -195,7 +215,9 @@
         mCallButtonPadding = a.getDimensionPixelOffset(
                 R.styleable.ContactListItemView_list_item_call_button_padding, 0);
         mPresenceIconMargin = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_presence_icon_margin, 0);
+                R.styleable.ContactListItemView_list_item_presence_icon_margin, 4);
+        mPresenceIconSize = a.getDimensionPixelOffset(
+                R.styleable.ContactListItemView_list_item_presence_icon_size, 16);
         mDefaultPhotoViewSize = a.getDimensionPixelOffset(
                 R.styleable.ContactListItemView_list_item_photo_size, 0);
         mHeaderTextIndent = a.getDimensionPixelOffset(
@@ -204,14 +226,18 @@
                 R.styleable.ContactListItemView_list_item_header_text_color, Color.BLACK);
         mHeaderTextSize = a.getDimensionPixelSize(
                 R.styleable.ContactListItemView_list_item_header_text_size, 12);
+        mHeaderBackgroundHeight = a.getDimensionPixelSize(
+                R.styleable.ContactListItemView_list_item_header_height, 30);
+        mHeaderUnderlineHeight = a.getDimensionPixelSize(
+                R.styleable.ContactListItemView_list_item_header_underline_height, 1);
+        mHeaderUnderlineColor = a.getColor(
+                R.styleable.ContactListItemView_list_item_header_underline_color, 0);
 
         mPrefixHighligher = new PrefixHighlighter(
                 a.getColor(R.styleable.ContactListItemView_list_item_prefix_highlight_color,
                         Color.GREEN));
-
         a.recycle();
 
-        mHeaderBackgroundHeight = mHeaderBackgroundDrawable.getIntrinsicHeight();
         mHorizontalDividerHeight = mHorizontalDividerDrawable.getIntrinsicHeight();
 
         if (mActivatedBackgroundDrawable != null) {
@@ -248,63 +274,80 @@
         int height = 0;
         int preferredHeight = mPreferredHeight;
 
-        mLine1Height = 0;
-        mLine2Height = 0;
-        mLine3Height = 0;
-        mLine4Height = 0;
+        mNameTextViewHeight = 0;
+        mPhoneticNameTextViewHeight = 0;
+        mLabelTextViewHeight = 0;
+        mSnippetTextViewHeight = 0;
+        mStatusTextViewHeight = 0;
 
-        // Obtain the natural dimensions of the name text (we only care about height)
+        // Go over all visible text views and add their heights to get the total height
         if (isVisible(mNameTextView)) {
             mNameTextView.measure(0, 0);
-            mLine1Height = mNameTextView.getMeasuredHeight();
+            mNameTextViewHeight = mNameTextView.getMeasuredHeight();
         }
 
         if (isVisible(mPhoneticNameTextView)) {
             mPhoneticNameTextView.measure(0, 0);
-            mLine2Height = mPhoneticNameTextView.getMeasuredHeight();
+            mPhoneticNameTextViewHeight = mPhoneticNameTextView.getMeasuredHeight();
         }
 
         if (isVisible(mLabelView)) {
             mLabelView.measure(0, 0);
-            mLine3Height = mLabelView.getMeasuredHeight();
+            mLabelTextViewHeight = mLabelView.getMeasuredHeight();
         }
 
+        // Label view height is the biggest of the label text view and the data text view
         if (isVisible(mDataView)) {
             mDataView.measure(0, 0);
-            mLine3Height = Math.max(mLine3Height, mDataView.getMeasuredHeight());
+            mLabelTextViewHeight = Math.max(mLabelTextViewHeight, mDataView.getMeasuredHeight());
         }
 
         if (isVisible(mSnippetView)) {
             mSnippetView.measure(0, 0);
-            mLine4Height = mSnippetView.getMeasuredHeight();
+            mSnippetTextViewHeight = mSnippetView.getMeasuredHeight();
         }
 
-        height += mLine1Height + mLine2Height + mLine3Height + mLine4Height
-                + mPaddingTop + mPaddingBottom;
+        // Status view height is the biggest of the text view and the presence icon
+        if (isVisible(mPresenceIcon)) {
+            mPresenceIcon.measure(mPresenceIconSize, mPresenceIconSize);
+            mStatusTextViewHeight = mPresenceIcon.getMeasuredHeight();
+        }
+
+        if (isVisible(mStatusView)) {
+            mStatusView.measure(0, 0);
+            mStatusTextViewHeight = Math.max(mStatusTextViewHeight,
+                    mStatusView.getMeasuredHeight());
+        }
+
+        // Calculate height including padding
+        height += mNameTextViewHeight + mPhoneticNameTextViewHeight + mLabelTextViewHeight +
+                mSnippetTextViewHeight + mStatusTextViewHeight + mPaddingTop + mPaddingBottom;
 
         if (isVisible(mCallButton)) {
             mCallButton.measure(0, 0);
         }
-        if (isVisible(mPresenceIcon)) {
-            mPresenceIcon.measure(0, 0);
-        }
 
+        // Make sure the height is at least as high as the photo
         ensurePhotoViewSize();
-
         height = Math.max(height, mPhotoViewHeight + mPaddingBottom + mPaddingTop);
 
+        // Add horizontal divider height
         if (mHorizontalDividerVisible) {
             height += mHorizontalDividerHeight;
             preferredHeight += mHorizontalDividerHeight;
         }
 
+        // Make sure height is at least the preferred height
         height = Math.max(height, preferredHeight);
 
+        // Add the height of the header if visible
         if (mHeaderVisible) {
             mHeaderTextView.measure(
                     MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
-            height += mHeaderBackgroundHeight;
+            mHeaderBackgroundHeight = Math.max(mHeaderBackgroundHeight,
+                    mHeaderTextView.getMeasuredHeight());
+            height += (mHeaderBackgroundHeight + mHeaderUnderlineHeight);
         }
 
         setMeasuredDimension(width, height);
@@ -318,22 +361,27 @@
         // Determine the vertical bounds by laying out the header first.
         int topBound = 0;
         int bottomBound = height;
+        int leftBound = mPaddingLeft;
 
+        // Put the header in the top of the contact view (Text + underline view)
         if (mHeaderVisible) {
-            mHeaderBackgroundDrawable.setBounds(
+            mHeaderTextView.layout(leftBound + mHeaderTextIndent,
                     0,
-                    0,
-                    width,
+                    width - mPaddingRight,
                     mHeaderBackgroundHeight);
-            mHeaderTextView.layout(mHeaderTextIndent, 0, width, mHeaderBackgroundHeight);
-            topBound += mHeaderBackgroundHeight;
+            mHeaderDivider.layout(leftBound,
+                    mHeaderBackgroundHeight,
+                    width - mPaddingRight,
+                    mHeaderBackgroundHeight + mHeaderUnderlineHeight);
+            topBound += (mHeaderBackgroundHeight + mHeaderUnderlineHeight);
         }
 
+        // Put horizontal divider at the bottom
         if (mHorizontalDividerVisible) {
             mHorizontalDividerDrawable.setBounds(
-                    0,
+                    leftBound,
                     height - mHorizontalDividerHeight,
-                    width,
+                    width - mPaddingRight,
                     height);
             bottomBound -= mHorizontalDividerHeight;
         }
@@ -344,92 +392,114 @@
             mActivatedBackgroundDrawable.setBounds(mBoundsWithoutHeader);
         }
 
+        // Set the top/bottom paddings
         topBound += mPaddingTop;
         bottomBound -= mPaddingBottom;
 
-        // Positions of views on the left are fixed and so are those on the right side.
-        // The stretchable part of the layout is in the middle.  So, we will start off
-        // by laying out the left and right sides. Then we will allocate the remainder
-        // to the text fields in the middle.
+        // Set contact layout:
+        // Photo on the right, call button to the left of the photo
+        // Text aligned to the left along with the presence status.
 
-        int leftBound = layoutLeftSide(height, topBound, bottomBound, mPaddingLeft);
-        int rightBound = layoutRightSide(height, topBound, width);
-
-        // Text lines, centered vertically
-        rightBound -= mPaddingRight;
+        // layout the photo and call button.
+        int rightBound = layoutRightSide(height, topBound, bottomBound, width - mPaddingRight);
 
         // Center text vertically
-        int totalTextHeight = mLine1Height + mLine2Height + mLine3Height + mLine4Height;
+        int totalTextHeight = mNameTextViewHeight + mPhoneticNameTextViewHeight +
+                mLabelTextViewHeight + mSnippetTextViewHeight + mStatusTextViewHeight;
         int textTopBound = (bottomBound + topBound - totalTextHeight) / 2;
 
+        // Layout all text view and presence icon
+        // Put name TextView first
         if (isVisible(mNameTextView)) {
             mNameTextView.layout(leftBound,
                     textTopBound,
                     rightBound,
-                    textTopBound + mLine1Height);
+                    textTopBound + mNameTextViewHeight);
+            textTopBound += mNameTextViewHeight;
         }
 
+        // Presence and status
+        int statusLeftBound = leftBound;
+        if (isVisible(mPresenceIcon)) {
+            int iconWidth = mPresenceIcon.getMeasuredWidth();
+            mPresenceIcon.layout(
+                    leftBound,
+                    textTopBound,
+                    leftBound + iconWidth,
+                    textTopBound + mStatusTextViewHeight);
+            statusLeftBound += (iconWidth + mPresenceIconMargin);
+        }
+
+        if (isVisible(mStatusView)) {
+            mStatusView.layout(statusLeftBound,
+                    textTopBound,
+                    rightBound,
+                    textTopBound + mStatusTextViewHeight);
+        }
+
+        if (isVisible(mStatusView) || isVisible(mPresenceIcon)) {
+            textTopBound += mStatusTextViewHeight;
+        }
+
+        // Rest of text views
         int dataLeftBound = leftBound;
         if (isVisible(mPhoneticNameTextView)) {
             mPhoneticNameTextView.layout(leftBound,
-                    textTopBound + mLine1Height,
+                    textTopBound,
                     rightBound,
-                    textTopBound + mLine1Height + mLine2Height);
+                    textTopBound + mPhoneticNameTextViewHeight);
+            textTopBound += mPhoneticNameTextViewHeight;
         }
 
         if (isVisible(mLabelView)) {
             dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
             mLabelView.layout(leftBound,
-                    textTopBound + mLine1Height + mLine2Height,
+                    textTopBound,
                     dataLeftBound,
-                    textTopBound + mLine1Height + mLine2Height + mLine3Height);
+                    textTopBound + mLabelTextViewHeight);
             dataLeftBound += mGapBetweenLabelAndData;
         }
 
         if (isVisible(mDataView)) {
             mDataView.layout(dataLeftBound,
-                    textTopBound + mLine1Height + mLine2Height,
+                    textTopBound,
                     rightBound,
-                    textTopBound + mLine1Height + mLine2Height + mLine3Height);
+                    textTopBound + mLabelTextViewHeight);
+        }
+        if (isVisible(mLabelView) || isVisible(mDataView)) {
+            textTopBound += mLabelTextViewHeight;
         }
 
         if (isVisible(mSnippetView)) {
             mSnippetView.layout(leftBound,
-                    textTopBound + mLine1Height + mLine2Height + mLine3Height,
+                    textTopBound,
                     rightBound,
-                    textTopBound + mLine1Height + mLine2Height + mLine3Height + mLine4Height);
+                    textTopBound + mSnippetTextViewHeight);
         }
     }
 
     /**
-     * Performs layout of the left side of the view
-     *
-     * @return new left boundary
-     */
-    protected int layoutLeftSide(int height, int topBound, int bottomBound, int leftBound) {
-        View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
-        if (photoView != null) {
-            // Center the photo vertically
-            int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
-            photoView.layout(
-                    leftBound,
-                    photoTop,
-                    leftBound + mPhotoViewWidth,
-                    photoTop + mPhotoViewHeight);
-            leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
-        } else if (mKeepHorizontalPaddingForPhotoView) {
-            // Draw nothing but keep the padding.
-            leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
-        }
-        return leftBound;
-    }
-
-    /**
      * Performs layout of the right side of the view
      *
      * @return new right boundary
      */
-    protected int layoutRightSide(int height, int topBound, int rightBound) {
+    protected int layoutRightSide(int height, int topBound, int bottomBound, int rightBound) {
+
+        // Photo is the right most view, set it up
+
+        View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
+        if (photoView != null) {
+            // Center the photo vertically
+            int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
+            photoView.layout(
+                    rightBound - mPhotoViewWidth,
+                    photoTop,
+                    rightBound,
+                    photoTop + mPhotoViewHeight);
+            rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
+        }
+
+        // Put call button and vertical divider
         if (isVisible(mCallButton)) {
             int buttonWidth = mCallButton.getMeasuredWidth();
             rightBound -= buttonWidth;
@@ -450,15 +520,6 @@
             mVerticalDividerVisible = false;
         }
 
-        if (isVisible(mPresenceIcon)) {
-            int iconWidth = mPresenceIcon.getMeasuredWidth();
-            rightBound -= mPresenceIconMargin + iconWidth;
-            mPresenceIcon.layout(
-                    rightBound,
-                    topBound,
-                    rightBound + iconWidth,
-                    height);
-        }
         return rightBound;
     }
 
@@ -545,9 +606,6 @@
         if (mActivatedStateSupported) {
             mActivatedBackgroundDrawable.draw(canvas);
         }
-        if (mHeaderVisible) {
-            mHeaderBackgroundDrawable.draw(canvas);
-        }
         if (mHorizontalDividerVisible) {
             mHorizontalDividerDrawable.draw(canvas);
         }
@@ -579,13 +637,22 @@
                 mHeaderTextView.setGravity(Gravity.CENTER_VERTICAL);
                 addView(mHeaderTextView);
             }
+            if (mHeaderDivider == null) {
+                mHeaderDivider = new View(mContext);
+                mHeaderDivider.setBackgroundColor(mHeaderUnderlineColor);
+                addView(mHeaderDivider);
+            }
             mHeaderTextView.setText(title);
             mHeaderTextView.setVisibility(View.VISIBLE);
+            mHeaderDivider.setVisibility(View.VISIBLE);
             mHeaderVisible = true;
         } else {
             if (mHeaderTextView != null) {
                 mHeaderTextView.setVisibility(View.GONE);
             }
+            if (mHeaderDivider != null) {
+                mHeaderDivider.setVisibility(View.GONE);
+            }
             mHeaderVisible = false;
         }
     }
@@ -599,7 +666,9 @@
         }
         if (mQuickContact == null) {
             mQuickContact = new QuickContactBadge(mContext, null, QUICK_CONTACT_BADGE_STYLE);
-            mQuickContact.setExcludeMimes(new String[] { Contacts.CONTENT_ITEM_TYPE });
+            mQuickContact.setExcludeMimes(new String[] {
+                Contacts.CONTENT_ITEM_TYPE
+            });
             addView(mQuickContact);
             mPhotoViewWidthAndHeightAreReady = false;
         }
@@ -634,10 +703,10 @@
     /**
      * Removes the photo view.
      *
-     * @param keepHorizontalPadding True means data on the right side will have padding on left,
-     * pretending there is still a photo view.
-     * @param keepVerticalPadding True means the View will have some height enough for
-     * accommodating a photo view.
+     * @param keepHorizontalPadding True means data on the right side will have
+     *            padding on left, pretending there is still a photo view.
+     * @param keepVerticalPadding True means the View will have some height
+     *            enough for accommodating a photo view.
      */
     public void removePhotoView(boolean keepHorizontalPadding, boolean keepVerticalPadding) {
         mPhotoViewWidthAndHeightAreReady = false;
@@ -838,6 +907,37 @@
     }
 
     /**
+     * Returns the text view for the status, creating it if necessary.
+     */
+
+    public TextView getStatusView() {
+        if (mStatusView == null) {
+            mStatusView = new TextView(mContext);
+            mStatusView.setSingleLine(true);
+            mStatusView.setEllipsize(getTextEllipsis());
+            mStatusView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
+            mStatusView.setText("Put Status here");   // Temporary
+            addView(mStatusView);
+        }
+        return mStatusView;
+    }
+
+    /**
+     * Adds or updates a text view for the status.
+     */
+    public void setStatus(CharSequence text) {
+        if (TextUtils.isEmpty(text)) {
+            if (mStatusView != null) {
+                mStatusView.setVisibility(View.GONE);
+            }
+        } else {
+            getStatusView();
+            mStatusView.setText(text);
+            mStatusView.setVisibility(VISIBLE);
+        }
+    }
+
+    /**
      * Adds or updates the presence icon view.
      */
     public void setPresence(Drawable icon) {
diff --git a/src/com/android/contacts/list/ContactListPinnedHeaderView.java b/src/com/android/contacts/list/ContactListPinnedHeaderView.java
index e850511..5d0d7b1 100644
--- a/src/com/android/contacts/list/ContactListPinnedHeaderView.java
+++ b/src/com/android/contacts/list/ContactListPinnedHeaderView.java
@@ -41,10 +41,14 @@
     private final int mHeaderTextColor;
     private final int mHeaderTextIndent;
     private final int mHeaderTextSize;
+    private final int mHeaderUnderlineHeight;
+    private final int mHeaderUnderlineColor;
+    private final int mPaddingRight;
+    private final int mPaddingLeft;
 
-    private Drawable mHeaderBackgroundDrawable;
     private int mHeaderBackgroundHeight;
     private TextView mHeaderTextView;
+    private View mHeaderDivider;
 
     public ContactListPinnedHeaderView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -52,25 +56,34 @@
 
         TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ContactListItemView);
 
-        mHeaderBackgroundDrawable = a.getDrawable(
-                R.styleable.ContactListItemView_section_header_background);
         mHeaderTextIndent = a.getDimensionPixelOffset(
                 R.styleable.ContactListItemView_list_item_header_text_indent, 0);
         mHeaderTextColor = a.getColor(
                 R.styleable.ContactListItemView_list_item_header_text_color, Color.BLACK);
         mHeaderTextSize = a.getDimensionPixelSize(
                 R.styleable.ContactListItemView_list_item_header_text_size, 12);
+        mHeaderUnderlineHeight = a.getDimensionPixelSize(
+                R.styleable.ContactListItemView_list_item_header_underline_height, 1);
+        mHeaderUnderlineColor = a.getColor(
+                R.styleable.ContactListItemView_list_item_header_underline_color, 0);
+        mHeaderBackgroundHeight = a.getDimensionPixelSize(
+                R.styleable.ContactListItemView_list_item_header_height, 30);
+        mPaddingLeft = a.getDimensionPixelOffset(
+                R.styleable.ContactListItemView_list_item_padding_left, 0);
+        mPaddingRight = a.getDimensionPixelOffset(
+                R.styleable.ContactListItemView_list_item_padding_right, 0);
 
         a.recycle();
 
-        mHeaderBackgroundHeight = mHeaderBackgroundDrawable.getIntrinsicHeight();
-
         mHeaderTextView = new TextView(mContext);
         mHeaderTextView.setTextColor(mHeaderTextColor);
         mHeaderTextView.setTextSize(mHeaderTextSize);
         mHeaderTextView.setTypeface(mHeaderTextView.getTypeface(), Typeface.BOLD);
         mHeaderTextView.setGravity(Gravity.CENTER_VERTICAL);
         addView(mHeaderTextView);
+        mHeaderDivider = new View(mContext);
+        mHeaderDivider.setBackgroundColor(mHeaderUnderlineColor);
+        addView(mHeaderDivider);
     }
 
     @Override
@@ -83,20 +96,21 @@
                 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                 MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
 
-        setMeasuredDimension(width, mHeaderBackgroundHeight);
+        setMeasuredDimension(width, mHeaderBackgroundHeight + mHeaderUnderlineHeight);
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        int width = right - left;
-        mHeaderBackgroundDrawable.setBounds(0, 0, width, mHeaderBackgroundHeight);
-        mHeaderTextView.layout(mHeaderTextIndent, 0, width, mHeaderBackgroundHeight);
-    }
+        int width = right - left - mPaddingRight;
 
-    @Override
-    public void dispatchDraw(Canvas canvas) {
-        mHeaderBackgroundDrawable.draw(canvas);
-        super.dispatchDraw(canvas);
+        mHeaderTextView.layout(mHeaderTextIndent + mPaddingLeft,
+                0,
+                width,
+                mHeaderBackgroundHeight);
+        mHeaderDivider.layout(mPaddingLeft,
+                mHeaderBackgroundHeight,
+                width,
+                mHeaderBackgroundHeight + mHeaderUnderlineHeight);
     }
 
     /**
@@ -106,8 +120,10 @@
         if (!TextUtils.isEmpty(title)) {
             mHeaderTextView.setText(title);
             mHeaderTextView.setVisibility(View.VISIBLE);
+            mHeaderDivider.setVisibility(View.VISIBLE);
         } else {
             mHeaderTextView.setVisibility(View.GONE);
+            mHeaderDivider.setVisibility(View.GONE);
         }
     }
 
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 88531e1..d625aba 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -32,7 +32,7 @@
  */
 public class DefaultContactBrowseListFragment extends ContactBrowseListFragment {
 
-    private View mCounterHeaderView;
+    private TextView mCounterHeaderView;
     private View mSearchHeaderView;
     private TextView mAccountFilterHeaderView;
 
@@ -69,8 +69,7 @@
         // Putting the header view inside a container will allow us to make
         // it invisible later. See checkHeaderViewVisibility()
         FrameLayout headerContainer = new FrameLayout(inflater.getContext());
-        mCounterHeaderView = inflater.inflate(R.layout.total_contacts, null, false);
-        headerContainer.addView(mCounterHeaderView);
+        mCounterHeaderView = (TextView) getView().findViewById(R.id.contacts_count);
         mSearchHeaderView = inflater.inflate(R.layout.search_header, null, false);
         headerContainer.addView(mSearchHeaderView);
         getListView().addHeaderView(headerContainer, null, false);
@@ -111,35 +110,34 @@
     protected void showCount(int partitionIndex, Cursor data) {
         if (!isSearchMode() && data != null) {
             int count = data.getCount();
-            TextView textView = (TextView) mCounterHeaderView.findViewById(R.id.totalContactsText);
             if (count != 0) {
                 String format = getResources().getQuantityText(
                         R.plurals.listTotalAllContacts, count).toString();
-                textView.setText(String.format(format, count));
+                mCounterHeaderView.setText(String.format(format, count));
             } else {
                 ContactListFilter filter = getFilter();
                 int filterType = filter != null ? filter.filterType
                         : ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS;
                 switch (filterType) {
                     case ContactListFilter.FILTER_TYPE_ACCOUNT:
-                        textView.setText(getString(
+                        mCounterHeaderView.setText(getString(
                                 R.string.listTotalAllContactsZeroGroup, filter.accountName));
                         break;
                     case ContactListFilter.FILTER_TYPE_GROUP:
-                        textView.setText(
+                        mCounterHeaderView.setText(
                                 getString(R.string.listTotalAllContactsZeroGroup, filter.title));
                         break;
                     case ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY:
-                        textView.setText(R.string.listTotalPhoneContactsZero);
+                        mCounterHeaderView.setText(R.string.listTotalPhoneContactsZero);
                         break;
                     case ContactListFilter.FILTER_TYPE_STARRED:
-                        textView.setText(R.string.listTotalAllContactsZeroStarred);
+                        mCounterHeaderView.setText(R.string.listTotalAllContactsZeroStarred);
                         break;
                     case ContactListFilter.FILTER_TYPE_CUSTOM:
-                        textView.setText(R.string.listTotalAllContactsZeroCustom);
+                        mCounterHeaderView.setText(R.string.listTotalAllContactsZeroCustom);
                         break;
                     default:
-                        textView.setText(R.string.listTotalAllContactsZero);
+                        mCounterHeaderView.setText(R.string.listTotalAllContactsZero);
                         break;
                 }
             }
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
index 92118c0..d59aebd 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
@@ -20,6 +20,7 @@
 import com.android.contacts.R;
 import com.android.contacts.util.ContactBadgeUtil;
 import com.android.contacts.util.DataStatus;
+import com.android.contacts.util.StreamItemEntry;
 
 import android.app.PendingIntent;
 import android.appwidget.AppWidgetManager;
@@ -44,6 +45,7 @@
 import android.widget.RemoteViews;
 
 import java.util.HashMap;
+import java.util.List;
 
 public class SocialWidgetProvider extends AppWidgetProvider {
     private static final String TAG = "SocialWidgetProvider";
@@ -109,7 +111,7 @@
             // Not yet set-up (this can happen while the Configuration activity is visible)
             return;
         }
-        final ContactLoader contactLoader = new ContactLoader(context, contactUri);
+        final ContactLoader contactLoader = new ContactLoader(context, contactUri, false, true);
         contactLoader.registerListener(0,
                 new ContactLoader.OnLoadCompleteListener<ContactLoader.Result>() {
                     @Override
@@ -140,8 +142,13 @@
             setPhoto(views, photo != null
                     ? BitmapFactory.decodeByteArray(photo, 0, photo.length)
                             : ContactBadgeUtil.loadPlaceholderPhoto(context));
-            setStatusAttribution(views, ContactBadgeUtil.getSocialDate(
-                    contactData, context));
+
+            // TODO: Rotate between all the stream items?
+            StreamItemEntry streamItem = null;
+            if (!contactData.getStreamItems().isEmpty()) {
+                streamItem = contactData.getStreamItems().get(0);
+                setStatusAttribution(views, ContactBadgeUtil.getSocialDate(streamItem, context));
+            }
 
             // OnClick launch QuickContact
             final Intent intent = new Intent(QuickContact.ACTION_QUICK_CONTACT);
@@ -157,7 +164,7 @@
             views.setOnClickPendingIntent(R.id.border, pendingIntent);
 
             setDisplayNameAndSnippet(context, views, contactData.getDisplayName(),
-                    contactData.getPhoneticName(), contactData.getStatuses(), pendingIntent);
+                    contactData.getPhoneticName(), contactData.getStreamItems(), pendingIntent);
         }
 
         // Configure UI
@@ -174,7 +181,7 @@
      */
     private static void setDisplayNameAndSnippet(Context context, RemoteViews views,
             CharSequence displayName, CharSequence phoneticName,
-            HashMap<Long, DataStatus> statuses, PendingIntent defaultIntent) {
+            List<StreamItemEntry> streamItems, PendingIntent defaultIntent) {
         SpannableStringBuilder sb = new SpannableStringBuilder();
 
         CharSequence name = displayName;
@@ -190,27 +197,15 @@
         sb.setSpan(sizeSpan, 0, name.length(), 0);
         sb.setSpan(styleSpan, 0, name.length(), 0);
 
-        long latestStatusId = 0;
-        DataStatus latestStatus = null;
-        if (statuses != null) {
-            for (HashMap.Entry<Long, DataStatus> entry : statuses.entrySet()) {
-                DataStatus status = entry.getValue();
-                if (!TextUtils.isEmpty(status.getStatus())
-                        && (latestStatus == null
-                                || latestStatus.getTimestamp() < status.getTimestamp())) {
-                    latestStatusId = entry.getKey();
-                    latestStatus = status;
-                }
-            }
-        }
-
-        if (latestStatus == null) {
+        if (streamItems.isEmpty()) {
             views.setTextViewText(R.id.name, sb);
             views.setViewVisibility(R.id.name, View.VISIBLE);
             views.setViewVisibility(R.id.name_and_snippet, View.GONE);
             views.setOnClickPendingIntent(R.id.widget_container, defaultIntent);
         } else {
-            CharSequence status = latestStatus.getStatus();
+            // TODO: Rotate between all the stream items?
+            StreamItemEntry streamItem = streamItems.get(0);
+            CharSequence status = streamItem.getText();
             if (status.length() <= SHORT_SNIPPET_LENGTH) {
                 sb.append("\n");
             } else {
@@ -220,11 +215,13 @@
             views.setTextViewText(R.id.name_and_snippet, sb);
             views.setViewVisibility(R.id.name, View.GONE);
             views.setViewVisibility(R.id.name_and_snippet, View.VISIBLE);
-            final Intent intent = new Intent(Intent.ACTION_VIEW,
-                    ContentUris.withAppendedId(Data.CONTENT_URI, latestStatusId));
-
-            views.setOnClickPendingIntent(R.id.name_and_snippet_container,
-                    PendingIntent.getActivity(context, 0, intent, 0));
+            if (!TextUtils.isEmpty(streamItem.getAction())
+                    && !TextUtils.isEmpty(streamItem.getActionUri())) {
+                final Intent intent = new Intent(streamItem.getAction(),
+                        Uri.parse(streamItem.getActionUri()));
+                views.setOnClickPendingIntent(R.id.name_and_snippet_container,
+                        PendingIntent.getActivity(context, 0, intent, 0));
+            }
         }
     }
 
diff --git a/src/com/android/contacts/util/ContactBadgeUtil.java b/src/com/android/contacts/util/ContactBadgeUtil.java
index 65025f8..2f37b4c 100644
--- a/src/com/android/contacts/util/ContactBadgeUtil.java
+++ b/src/com/android/contacts/util/ContactBadgeUtil.java
@@ -37,17 +37,11 @@
     private static final String TAG = "ContactBadgeUtil";
 
     /**
-     * Returns the social snippet attribution, including the date
+     * Returns the social snippet attribution for the given stream item entry, including the date.
      */
-    public static CharSequence getSocialDate(ContactLoader.Result contactData,
-            Context context) {
-        if (TextUtils.isEmpty(contactData.getSocialSnippet())) {
-            return null;
-        }
-
+    public static CharSequence getSocialDate(StreamItemEntry streamItem, Context context) {
         final CharSequence timestampDisplayValue;
-
-        final Long statusTimestamp = contactData.getStatusTimestamp();
+        final Long statusTimestamp = streamItem.getTimestamp();
         if (statusTimestamp  != null) {
             // Set the date/time field by mixing relative and absolute
             // times.
@@ -63,8 +57,8 @@
 
         String labelDisplayValue = null;
 
-        final Integer statusLabel = contactData.getStatusLabel();
-        final String statusResPackage = contactData.getStatusResPackage();
+        final Integer statusLabel = streamItem.getLabelRes();
+        final String statusResPackage = streamItem.getResPackage();
         if (statusLabel  != null) {
             Resources resources;
             if (TextUtils.isEmpty(statusResPackage)) {
diff --git a/src/com/android/contacts/util/StreamItemEntry.java b/src/com/android/contacts/util/StreamItemEntry.java
new file mode 100644
index 0000000..dc54229
--- /dev/null
+++ b/src/com/android/contacts/util/StreamItemEntry.java
@@ -0,0 +1,141 @@
+/*
+ * 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.util;
+
+import android.database.Cursor;
+import android.provider.ContactsContract.StreamItems;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Data object for a social stream item.  Social stream items may contain multiple
+ * mPhotos.  Social stream item entries are comparable; entries with more recent
+ * timestamps will be displayed on top.
+ */
+public class StreamItemEntry implements Comparable<StreamItemEntry> {
+
+    // Basic stream item fields.
+    private final long mId;
+    private final String mText;
+    private final String mComments;
+    private final long mTimestamp;
+    private final String mAction;
+    private final String mActionUri;
+
+    // Package references for label and icon resources.
+    private final String mResPackage;
+    private final int mIconRes;
+    private final int mLabelRes;
+
+    // Photos associated with this stream item.
+    private List<StreamItemPhotoEntry> mPhotos;
+
+    public StreamItemEntry(long id, String text, String comments, long timestamp, String action,
+            String actionUri, String resPackage, int iconRes, int labelRes) {
+        mId = id;
+        mText = text;
+        mComments = comments;
+        mTimestamp = timestamp;
+        mAction = action;
+        mActionUri = actionUri;
+        mResPackage = resPackage;
+        mIconRes = iconRes;
+        mLabelRes = labelRes;
+        mPhotos = new ArrayList<StreamItemPhotoEntry>();
+    }
+
+    public StreamItemEntry(Cursor cursor) {
+        // This is expected to be populated via a cursor containing all StreamItems columns in
+        // its projection.
+        mId = getLong(cursor, StreamItems._ID);
+        mText = getString(cursor, StreamItems.TEXT);
+        mComments = getString(cursor, StreamItems.COMMENTS);
+        mTimestamp = getLong(cursor, StreamItems.TIMESTAMP);
+        mAction = getString(cursor, StreamItems.ACTION);
+        mActionUri = getString(cursor, StreamItems.ACTION_URI);
+        mResPackage = getString(cursor, StreamItems.RES_PACKAGE);
+        mIconRes = getInt(cursor, StreamItems.RES_ICON, -1);
+        mLabelRes = getInt(cursor, StreamItems.RES_LABEL, -1);
+        mPhotos = new ArrayList<StreamItemPhotoEntry>();
+    }
+
+    public void addPhoto(StreamItemPhotoEntry photoEntry) {
+        mPhotos.add(photoEntry);
+    }
+
+    @Override
+    public int compareTo(StreamItemEntry other) {
+        return mTimestamp == other.mTimestamp ? 0 : mTimestamp > other.mTimestamp ? -1 : 1;
+    }
+
+    public long getId() {
+        return mId;
+    }
+
+    public String getText() {
+        return mText;
+    }
+
+    public String getComments() {
+        return mComments;
+    }
+
+    public long getTimestamp() {
+        return mTimestamp;
+    }
+
+    public String getAction() {
+        return mAction;
+    }
+
+    public String getActionUri() {
+        return mActionUri;
+    }
+
+    public String getResPackage() {
+        return mResPackage;
+    }
+
+    public int getIconRes() {
+        return mIconRes;
+    }
+
+    public int getLabelRes() {
+        return mLabelRes;
+    }
+
+    public List<StreamItemPhotoEntry> getPhotos() {
+        Collections.sort(mPhotos);
+        return mPhotos;
+    }
+
+    private static String getString(Cursor cursor, String columnName) {
+        return cursor.getString(cursor.getColumnIndex(columnName));
+    }
+
+    private static int getInt(Cursor cursor, String columnName, int missingValue) {
+        final int columnIndex = cursor.getColumnIndex(columnName);
+        return cursor.isNull(columnIndex) ? missingValue : cursor.getInt(columnIndex);
+    }
+
+    private static long getLong(Cursor cursor, String columnName) {
+        final int columnIndex = cursor.getColumnIndex(columnName);
+        return cursor.getLong(columnIndex);
+    }
+}
diff --git a/src/com/android/contacts/util/StreamItemPhotoEntry.java b/src/com/android/contacts/util/StreamItemPhotoEntry.java
new file mode 100644
index 0000000..6527454
--- /dev/null
+++ b/src/com/android/contacts/util/StreamItemPhotoEntry.java
@@ -0,0 +1,139 @@
+/*
+ * 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.util;
+
+import android.database.Cursor;
+import android.provider.ContactsContract.PhotoFiles;
+import android.provider.ContactsContract.StreamItemPhotos;
+
+/**
+ * Data object for a photo associated with a social stream item.  These are comparable;
+ * entries with a lower sort index will be displayed on top (with the ID used as a
+ * tie-breaker).
+ */
+public class StreamItemPhotoEntry implements Comparable<StreamItemPhotoEntry> {
+    private final long mId;
+    private final int mSortIndex;
+    private final long mPhotoFileId;
+    private final String mPhotoUri;
+    private final int mHeight;
+    private final int mWidth;
+    private final int mFileSize;
+    private final String mAction;
+    private final String mActionUri;
+
+    public StreamItemPhotoEntry(long id, int sortIndex, long photoFileId, String photoUri,
+            int height, int width, int fileSize, String action, String actionUri) {
+        mId = id;
+        mSortIndex = sortIndex;
+        mPhotoFileId = photoFileId;
+        mPhotoUri = photoUri;
+        mHeight = height;
+        mWidth = width;
+        mFileSize = fileSize;
+        mAction = action;
+        mActionUri = actionUri;
+    }
+
+    public StreamItemPhotoEntry(Cursor cursor) {
+        // This is expected to be populated via a cursor containing a join of all
+        // StreamItemPhotos columns and all PhotoFiles columns (except for ID).
+        mId = getLong(cursor, StreamItemPhotos._ID);
+        mSortIndex = getInt(cursor, StreamItemPhotos.SORT_INDEX, -1);
+        mPhotoFileId = getLong(cursor, StreamItemPhotos.PHOTO_FILE_ID);
+        mPhotoUri = getString(cursor, StreamItemPhotos.PHOTO_URI);
+        mHeight = getInt(cursor, PhotoFiles.HEIGHT, -1);
+        mWidth = getInt(cursor, PhotoFiles.WIDTH, -1);
+        mFileSize = getInt(cursor, PhotoFiles.FILESIZE, -1);
+        mAction = getString(cursor, StreamItemPhotos.ACTION);
+        mActionUri = getString(cursor, StreamItemPhotos.ACTION_URI);
+    }
+
+    public long getId() {
+        return mId;
+    }
+
+    public int getSortIndex() {
+        return mSortIndex;
+    }
+
+    public long getPhotoFileId() {
+        return mPhotoFileId;
+    }
+
+    public String getPhotoUri() {
+        return mPhotoUri;
+    }
+
+    public int getHeight() {
+        return mHeight;
+    }
+
+    public int getWidth() {
+        return mWidth;
+    }
+
+    public int getFileSize() {
+        return mFileSize;
+    }
+
+    public String getAction() {
+        return mAction;
+    }
+
+    public String getActionUri() {
+        return mActionUri;
+    }
+
+    @Override
+    public int compareTo(StreamItemPhotoEntry streamItemPhotoEntry) {
+        // Sort index is used to compare, falling back to ID if neither entry has a
+        // sort index specified (entries without a sort index are sorted after entries
+        // that have one).
+        if (mSortIndex == streamItemPhotoEntry.mSortIndex) {
+            if (mSortIndex == -1) {
+                return mId == streamItemPhotoEntry.mId ? 0
+                        : mId < streamItemPhotoEntry.mId ? -1 : 1;
+            } else {
+                return 0;
+            }
+        } else {
+            if (mSortIndex == -1) {
+                return 1;
+            }
+            if (streamItemPhotoEntry.mSortIndex == -1) {
+                return -1;
+            }
+            return mSortIndex == streamItemPhotoEntry.mSortIndex ? 0
+                    : mSortIndex < streamItemPhotoEntry.mSortIndex ? -1 : 1;
+        }
+    }
+
+    private static String getString(Cursor cursor, String columnName) {
+        return cursor.getString(cursor.getColumnIndex(columnName));
+    }
+
+    private static int getInt(Cursor cursor, String columnName, int missingValue) {
+        final int columnIndex = cursor.getColumnIndex(columnName);
+        return cursor.isNull(columnIndex) ? missingValue : cursor.getInt(columnIndex);
+    }
+
+    private static long getLong(Cursor cursor, String columnName) {
+        final int columnIndex = cursor.getColumnIndex(columnName);
+        return cursor.getLong(columnIndex);
+    }
+}
diff --git a/tests/src/com/android/contacts/CallDetailActivityTest.java b/tests/src/com/android/contacts/CallDetailActivityTest.java
index a36216e..c279860 100644
--- a/tests/src/com/android/contacts/CallDetailActivityTest.java
+++ b/tests/src/com/android/contacts/CallDetailActivityTest.java
@@ -17,6 +17,8 @@
 package com.android.contacts;
 
 import com.android.contacts.util.IntegrationTestUtils;
+import com.android.contacts.util.LocaleTestUtils;
+import com.android.internal.view.menu.ContextMenuBuilder;
 import com.google.common.base.Preconditions;
 
 import android.app.Activity;
@@ -27,6 +29,9 @@
 import android.net.Uri;
 import android.provider.CallLog;
 import android.test.ActivityInstrumentationTestCase2;
+import android.view.Menu;
+
+import java.util.Locale;
 
 /**
  * Unit tests for the {@link CallDetailActivity}.
@@ -35,6 +40,7 @@
     private static final String FAKE_VOICEMAIL_URI_STRING = "content://fake_uri";
     private Uri mUri;
     private IntegrationTestUtils mTestUtils;
+    private LocaleTestUtils mLocaleTestUtils;
 
     public CallDetailActivityTest() {
         super(CallDetailActivity.class);
@@ -47,10 +53,16 @@
         // screenshots look weak.
         setActivityInitialTouchMode(true);
         mTestUtils = new IntegrationTestUtils(getInstrumentation());
+        // Some of the tests rely on the text that appears on screen - safest to force a
+        // specific locale.
+        mLocaleTestUtils = new LocaleTestUtils(getInstrumentation().getTargetContext());
+        mLocaleTestUtils.setLocale(Locale.US);
     }
 
     @Override
     protected void tearDown() throws Exception {
+        mLocaleTestUtils.restoreLocale();
+        mLocaleTestUtils = null;
         cleanUpUri();
         mTestUtils = null;
         super.tearDown();
@@ -61,7 +73,7 @@
      * <p>
      * The repro steps for this crash were to open a voicemail that does not have an attachment,
      * then click the play button (which just reported an error), then after that try to adjust the
-     * rate.
+     * rate.  See http://b/5047879.
      */
     public void testClickIncreaseRateButtonWithInvalidVoicemailDoesNotCrash() throws Throwable {
         setActivityIntentForTestVoicemailEntry();
@@ -76,6 +88,30 @@
         getActivity();
     }
 
+    /**
+     * Test for bug where voicemails should not have remove-from-call-log entry.
+     * <p>
+     * See http://b/5054103.
+     */
+    public void testVoicemailDoesNotHaveRemoveFromCallLog() throws Throwable {
+        setActivityIntentForTestVoicemailEntry();
+        CallDetailActivity activity = getActivity();
+        Menu menu = new ContextMenuBuilder(activity);
+        activity.onCreateOptionsMenu(menu);
+        activity.onPrepareOptionsMenu(menu);
+        assertFalse(menu.findItem(R.id.remove_from_call_log).isVisible());
+    }
+
+    /** Test to check that I haven't broken the remove-from-call-log entry from regular calls. */
+    public void testRegularCallDoesHaveRemoveFromCallLog() throws Throwable {
+        setActivityIntentForTestCallEntry();
+        CallDetailActivity activity = getActivity();
+        Menu menu = new ContextMenuBuilder(activity);
+        activity.onCreateOptionsMenu(menu);
+        activity.onPrepareOptionsMenu(menu);
+        assertTrue(menu.findItem(R.id.remove_from_call_log).isVisible());
+    }
+
     private void setActivityIntentForTestCallEntry() {
         createTestCallEntry(false);
         setActivityIntent(new Intent(Intent.ACTION_VIEW, mUri));
diff --git a/tests/src/com/android/contacts/util/IntegrationTestUtils.java b/tests/src/com/android/contacts/util/IntegrationTestUtils.java
index 45dc981..a61ea57 100644
--- a/tests/src/com/android/contacts/util/IntegrationTestUtils.java
+++ b/tests/src/com/android/contacts/util/IntegrationTestUtils.java
@@ -27,9 +27,13 @@
 import android.content.Context;
 import android.os.PowerManager;
 import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
 
 import junit.framework.Assert;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.FutureTask;
@@ -118,4 +122,55 @@
             }
         }
     }
+
+    /**
+     * Gets all {@link TextView} objects whose {@link TextView#getText()} contains the given text as
+     * a substring.
+     */
+    public List<TextView> getTextViewsWithString(final Activity activity, final String text)
+            throws Throwable {
+        return runOnUiThreadAndGetTheResult(new Callable<List<TextView>>() {
+            @Override
+            public List<TextView> call() throws Exception {
+                List<TextView> matchingViews = new ArrayList<TextView>();
+                for (TextView textView : getAllViews(TextView.class, getRootView(activity))) {
+                    if (textView.getText().toString().contains(text)) {
+                        matchingViews.add(textView);
+                    }
+                }
+                return matchingViews;
+            }
+        });
+    }
+
+    /** Find the root view for a given activity. */
+    public static View getRootView(Activity activity) {
+        return activity.findViewById(android.R.id.content).getRootView();
+    }
+
+    /**
+     * Gets a list of all views of a given type, rooted at the given parent.
+     * <p>
+     * This method will recurse down through all {@link ViewGroup} instances looking for
+     * {@link View} instances of the supplied class type. Specifically it will use the
+     * {@link Class#isAssignableFrom(Class)} method as the test for which views to add to the list,
+     * so if you provide {@code View.class} as your type, you will get every view. The parent itself
+     * will be included also, should it be of the right type.
+     * <p>
+     * This call manipulates the ui, and as such should only be called from the application's main
+     * thread.
+     */
+    private static <T extends View> List<T> getAllViews(final Class<T> clazz, final View parent) {
+        List<T> results = new ArrayList<T>();
+        if (parent.getClass().equals(clazz)) {
+            results.add(clazz.cast(parent));
+        }
+        if (parent instanceof ViewGroup) {
+            ViewGroup viewGroup = (ViewGroup) parent;
+            for (int i = 0; i < viewGroup.getChildCount(); ++i) {
+                results.addAll(getAllViews(clazz, viewGroup.getChildAt(i)));
+            }
+        }
+        return results;
+    }
 }