Merge "Fix possible NPE on showDialpadChooser()"
diff --git a/res/layout/contact_tile_frequent.xml b/res/layout/contact_tile_frequent.xml
index 14cb04e..2e79ba6 100644
--- a/res/layout/contact_tile_frequent.xml
+++ b/res/layout/contact_tile_frequent.xml
@@ -17,7 +17,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     class="com.android.contacts.list.ContactTileView"
     android:focusable="true"
-    android:background="@drawable/list_selector"
+    android:background="@null"
     android:paddingRight="16dip"
     android:paddingLeft="16dip" >
 
@@ -32,32 +32,46 @@
             android:scaleType="centerCrop"
             android:layout_alignParentRight="true" />
 
-        <TextView
-            android:id="@+id/contact_tile_name"
+        <LinearLayout
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:textAppearance="?android:attr/textAppearanceMedium"
-            android:layout_toLeftOf="@id/contact_tile_quick" />
+            android:layout_height="64dip"
+            android:orientation="vertical"
+            android:layout_alignParentBottom="true"
+            android:gravity="center_vertical"
+            android:paddingRight="80dip"
+            android:paddingLeft="8dip">
 
-        <ImageView
-            android:id="@+id/contact_tile_presence"
-            android:layout_width="16dip"
-            android:layout_height="16dip"
-            android:layout_below="@id/contact_tile_name"
-            android:layout_alignParentLeft="true" />
+            <TextView
+                android:id="@+id/contact_tile_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@color/primary_text_color"
+                android:textSize="18sp"
+                android:singleLine="true"
+                android:fadingEdge="horizontal"
+                android:fadingEdgeLength="3dip"
+                android:ellipsize="marquee" />
 
-        <TextView
-            android:id="@+id/contact_tile_status"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textSize="14sp"
-            android:ellipsize="end"
-            android:singleLine="true"
-            android:textColor="#cccccc"
-            android:layout_toRightOf="@id/contact_tile_presence"
-            android:layout_below="@id/contact_tile_name" />
+            <TextView
+                android:id="@+id/contact_tile_status"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textColor="?android:attr/textColorSecondary"
+                android:singleLine="true"
+                android:drawablePadding="4dip"
+                android:fadingEdge="horizontal"
+                android:fadingEdgeLength="3dip"
+                android:ellipsize="marquee" />
+
+        </LinearLayout>
+
+        <View
+            android:id="@+id/contact_tile_horizontal_divider"
+            android:layout_width="match_parent"
+            android:layout_height="1px"
+            android:background="?android:attr/listDivider"
+            android:layout_below="@id/contact_tile_quick" />
 
     </RelativeLayout>
 
diff --git a/res/layout/contact_tile_frequent_phone.xml b/res/layout/contact_tile_frequent_phone.xml
index 50c1fc2..563dea0 100644
--- a/res/layout/contact_tile_frequent_phone.xml
+++ b/res/layout/contact_tile_frequent_phone.xml
@@ -17,7 +17,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     class="com.android.contacts.list.ContactTileView"
     android:focusable="true"
-    android:background="@drawable/list_selector"
+    android:background="@null"
     android:paddingRight="16dip"
     android:paddingLeft="16dip" >
 
@@ -36,22 +36,24 @@
             android:id="@+id/contact_tile_name"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:singleLine="true"
-            android:ellipsize="end"
             android:layout_marginLeft="8dip"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:layout_marginTop="8dip"
-            android:layout_toRightOf="@id/contact_tile_quick" />
+            android:layout_toRightOf="@id/contact_tile_quick"
+            android:singleLine="true"
+            android:fadingEdge="horizontal"
+            android:fadingEdgeLength="3dip"
+            android:ellipsize="marquee" />
 
         <TextView
             android:id="@+id/contact_tile_phone_number"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textSize="14sp"
-            android:ellipsize="end"
-            android:singleLine="true"
             android:textColor="@color/dialtacts_secondary_text_color"
             android:layout_marginLeft="8dip"
+            android:singleLine="true"
+            android:maxLength="18"
             android:layout_toRightOf="@id/contact_tile_quick"
             android:layout_below="@id/contact_tile_name" />
 
@@ -65,23 +67,16 @@
             android:textAllCaps="true"
             android:textColor="@color/dialtacts_secondary_text_color"
             android:layout_marginLeft="12dip"
+            android:maxLength="8"
             android:layout_alignParentRight="true"
             android:layout_alignTop="@id/contact_tile_phone_number" />
 
         <View
             android:id="@+id/contact_tile_horizontal_divider"
             android:layout_width="match_parent"
-            android:layout_height="1dip"
+            android:layout_height="1px"
             android:background="?android:attr/listDivider"
-            android:layout_alignParentBottom="true" />
-
-        <View
-            android:id="@+id/contact_tile_horizontal_picture_divider"
-            android:layout_width="64dip"
-            android:layout_height="1dip"
-            android:background="@android:color/black"
-            android:layout_alignParentBottom="true"
-            android:layout_alignParentLeft="true" />
+            android:layout_below="@id/contact_tile_quick" />
 
     </RelativeLayout>
 
diff --git a/res/layout/contact_tile_starred.xml b/res/layout/contact_tile_starred.xml
index 3b74946..5fdfe65 100644
--- a/res/layout/contact_tile_starred.xml
+++ b/res/layout/contact_tile_starred.xml
@@ -15,10 +15,13 @@
 -->
 <view
     xmlns:android="http://schemas.android.com/apk/res/android"
-    class="com.android.contacts.list.ContactTileStarredView"
-    style="@style/ContactTileStarred" >
+    android:background="@null"
+    android:paddingBottom="1dip"
+    android:paddingRight="1dip"
+    class="com.android.contacts.list.ContactTileStarredView" >
 
     <RelativeLayout
+        android:id="@+id/contact_tile_layout"
         android:layout_width="match_parent"
         android:layout_height="match_parent" >
 
@@ -28,40 +31,42 @@
             android:layout_height="match_parent"
             android:scaleType="centerCrop" />
 
-        <View
-            android:id="@+id/contact_tile_background"
+        <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="@dimen/contact_tile_shadowbox_height"
+            android:orientation="vertical"
+            android:background="@color/contact_tile_shadow_box_color"
             android:layout_alignParentBottom="true"
-            style="@style/ContactTileStarredShadowBox" />
+            android:gravity="center_vertical"
+            android:paddingRight="8dip"
+            android:paddingLeft="8dip">
 
-        <ImageView
-            android:id="@+id/contact_tile_presence"
-            android:layout_width="16dip"
-            android:layout_height="16dip"
-            android:layout_alignParentBottom="true"
-            android:layout_marginLeft="8dip"
-            android:layout_marginBottom="10dip" />
+            <TextView
+                android:id="@+id/contact_tile_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@android:color/white"
+                android:textSize="16sp"
+                android:singleLine="true"
+                android:fadingEdge="horizontal"
+                android:fadingEdgeLength="3dip"
+                android:ellipsize="marquee" />
 
-        <TextView
-            android:id="@+id/contact_tile_name"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignLeft="@id/contact_tile_presence"
-            android:layout_above="@id/contact_tile_presence"
-            android:textColor="@android:color/white"
-            stlye="@style/ContactTileStarredName" />
+            <TextView
+                android:id="@+id/contact_tile_status"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textColor="@color/people_contact_tile_status_color"
+                android:singleLine="true"
+                android:drawablePadding="4dip"
+                android:fadingEdge="horizontal"
+                android:fadingEdgeLength="3dip"
+                android:ellipsize="marquee" />
 
-        <TextView
-            android:id="@+id/contact_tile_status"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_toRightOf="@id/contact_tile_presence"
-            android:layout_below="@id/contact_tile_name"
-            android:layout_marginLeft="4dip"
-            style="@style/ContactTileStatusText" />
+        </LinearLayout>
 
-       <ImageButton
+       <View
             android:id="@+id/contact_tile_push_state"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
diff --git a/res/layout/contact_tile_starred_quick_contact.xml b/res/layout/contact_tile_starred_quick_contact.xml
index 0fb580e..9bb4abe 100644
--- a/res/layout/contact_tile_starred_quick_contact.xml
+++ b/res/layout/contact_tile_starred_quick_contact.xml
@@ -15,51 +15,61 @@
 -->
 <view
     xmlns:android="http://schemas.android.com/apk/res/android"
-    class="com.android.contacts.list.ContactTileStarredView"
-    style="@style/ContactTileStarred" >
+    android:paddingBottom="1dip"
+    android:paddingRight="1dip"
+    android:background="@null"
+    class="com.android.contacts.list.ContactTileStarredView" >
 
     <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent" >
 
-        <QuickContactBadge
-            android:id="@+id/contact_tile_quick"
+        <ImageView
+            android:id="@+id/contact_tile_image"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:scaleType="centerCrop" />
 
-        <View
-            android:id="@+id/contact_tile_background"
+        <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="@dimen/contact_tile_shadowbox_height"
+            android:orientation="vertical"
+            android:background="@color/contact_tile_shadow_box_color"
             android:layout_alignParentBottom="true"
-            style="@style/ContactTileStarredShadowBox" />
+            android:gravity="center_vertical"
+            android:paddingRight="8dip"
+            android:paddingLeft="8dip">
 
-        <ImageView
-            android:id="@+id/contact_tile_presence"
-            android:layout_width="16dip"
-            android:layout_height="16dip"
-            android:layout_alignParentBottom="true"
-            android:layout_marginLeft="8dip"
-            android:layout_marginBottom="10dip" />
+            <TextView
+                android:id="@+id/contact_tile_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@android:color/white"
+                android:textSize="16sp"
+                android:singleLine="true"
+                android:fadingEdge="horizontal"
+                android:fadingEdgeLength="3dip"
+                android:ellipsize="marquee" />
 
-        <TextView
-            android:id="@+id/contact_tile_name"
+            <TextView
+                android:id="@+id/contact_tile_status"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textColor="@color/people_contact_tile_status_color"
+                android:singleLine="true"
+                android:drawablePadding="4dip"
+                android:fadingEdge="horizontal"
+                android:fadingEdgeLength="3dip"
+                android:ellipsize="marquee" />
+
+        </LinearLayout>
+
+        <QuickContactBadge
+            android:id="@+id/contact_tile_quick"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignLeft="@id/contact_tile_presence"
-            android:layout_above="@id/contact_tile_presence"
-            android:textColor="@android:color/white"
-            stlye="@style/ContactTileStarredName" />
-
-        <TextView
-            android:id="@+id/contact_tile_status"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_toRightOf="@id/contact_tile_presence"
-            android:layout_below="@id/contact_tile_name"
-            android:layout_marginLeft="4dip"
-            style="@style/ContactTileStatusText" />
+            android:layout_height="match_parent"
+            android:background="@null" />
 
     </RelativeLayout>
 
diff --git a/res/layout/contact_tile_starred_secondary_target.xml b/res/layout/contact_tile_starred_secondary_target.xml
index 3558166..e72575e 100644
--- a/res/layout/contact_tile_starred_secondary_target.xml
+++ b/res/layout/contact_tile_starred_secondary_target.xml
@@ -15,8 +15,10 @@
 -->
 <view
     xmlns:android="http://schemas.android.com/apk/res/android"
-    class="com.android.contacts.list.ContactTileSecondaryTargetView"
-    style="@style/ContactTileStarred" >
+    android:background="@null"
+    android:paddingBottom="1dip"
+    android:paddingRight="1dip"
+    class="com.android.contacts.list.ContactTileSecondaryTargetView" >
 
     <RelativeLayout
         android:layout_width="match_parent"
@@ -28,49 +30,24 @@
             android:layout_height="match_parent"
             android:scaleType="centerCrop" />
 
-        <View
-            android:id="@+id/contact_tile_background"
+        <TextView
+            android:id="@+id/contact_tile_name"
             android:layout_width="match_parent"
             android:layout_height="@dimen/contact_tile_shadowbox_height"
+            android:background="@color/contact_tile_shadow_box_color"
+            android:gravity="center_vertical"
+            android:textColor="@android:color/white"
+            android:singleLine="true"
+            android:textSize="16sp"
+            android:fadingEdge="horizontal"
+            android:fadingEdgeLength="3dip"
+            android:ellipsize="marquee"
             android:layout_alignParentBottom="true"
-            android:layout_alignParentLeft="true"
-            style="@style/ContactTileStarredShadowBox" />
-
-        <FrameLayout
-            android:id="@+id/contact_tile_info_layout"
-            android:layout_height="@dimen/contact_tile_shadowbox_height"
-            android:layout_width="wrap_content"
-            android:orientation="horizontal"
-            android:layout_alignParentBottom="true"
-            android:layout_alignParentLeft="true"
-            android:layout_marginLeft="8dip"
-            android:layout_marginRight="45dip">
-
-            <TextView
-                android:id="@+id/contact_tile_name"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:textColor="@android:color/white"
-                android:singleLine="true"
-                android:textSize="16sp"
-                android:fadingEdge="horizontal"
-                android:fadingEdgeLength="3dip"
-                android:ellipsize="marquee" />
-
-        </FrameLayout>
+            android:paddingLeft="8dip"
+            android:paddingRight="47dip"
+            android:drawableRight="@drawable/ic_divider_dashed_holo_dark" />
 
         <View
-            android:id="@+id/secondary_vertical_divider"
-            android:layout_width="1dip"
-            android:layout_height="32dip"
-            android:layout_alignParentBottom="true"
-            android:layout_alignParentRight="true"
-            android:layout_marginRight="44dip"
-            android:layout_marginBottom="8dip"
-            android:background="@drawable/ic_divider_dashed_holo_dark" />
-
-        <ImageButton
             android:id="@+id/contact_tile_push_state"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
@@ -82,25 +59,11 @@
             android:background="?android:attr/selectableItemBackground"
             android:layout_height="@dimen/contact_tile_shadowbox_height"
             android:layout_width="48dip"
-            android:layout_alignParentBottom="true"
-            android:layout_alignParentRight="true"
             android:paddingRight="8dip"
-            android:paddingLeft="8dip" />
-
-        <View
-            android:id="@+id/contact_tile_vertical_divider"
-            android:layout_height="match_parent"
-            android:layout_width="2dip"
-            android:background="@color/phone_contact_tile_divider_color"
+            android:paddingLeft="8dip"
+            android:layout_alignParentBottom="true"
             android:layout_alignParentRight="true" />
 
-        <View
-            android:id="@+id/contact_tile_horizontal_divider"
-            android:layout_width="match_parent"
-            android:layout_height="2dip"
-            android:background="@color/phone_contact_tile_divider_color"
-            android:layout_alignParentBottom="true" />
-
     </RelativeLayout>
 
 </view>
diff --git a/res/layout/contacts_list_content.xml b/res/layout/contacts_list_content.xml
index 66c3936..cc0ebf4 100644
--- a/res/layout/contacts_list_content.xml
+++ b/res/layout/contacts_list_content.xml
@@ -24,7 +24,8 @@
     android:id="@+id/pinned_header_list_layout"
     android:orientation="vertical"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
+    android:layout_height="match_parent"
+    android:background="?attr/contact_browser_background" >
 
     <!-- Shown only when an Account filter is set.
          - paddingTop should be here to show "shade" effect correctly. -->
diff --git a/res/layout/list_separator.xml b/res/layout/list_separator.xml
index 7d57ef7..39de1da 100644
--- a/res/layout/list_separator.xml
+++ b/res/layout/list_separator.xml
@@ -23,22 +23,22 @@
     android:paddingLeft="16dip"
     android:paddingRight="16dip">
 
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingTop="8dip"
-            android:paddingLeft="8dip"
-            android:textStyle="bold"
-            android:textAllCaps="true"
-            android:singleLine="true"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="@color/people_app_theme_color"
-            android:gravity="left|center_vertical"
-            android:id="@+id/header_text" />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingTop="8dip"
+        android:paddingLeft="8dip"
+        android:textStyle="bold"
+        android:textAllCaps="true"
+        android:singleLine="true"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="@color/people_app_theme_color"
+        android:gravity="left|center_vertical"
+        android:id="@+id/header_text" />
 
-        <View
-            android:layout_width="match_parent"
-            android:layout_height="1dip"
-            android:background="@color/people_app_theme_color" />
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dip"
+        android:background="@color/people_app_theme_color" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/res/menu/call_log_options.xml b/res/menu/call_log_options.xml
index c62be77..c75c856 100644
--- a/res/menu/call_log_options.xml
+++ b/res/menu/call_log_options.xml
@@ -29,10 +29,4 @@
         android:id="@+id/show_all_calls"
         android:title="@string/menu_show_all_calls"
         android:showAsAction="withText" />
-
-    <item
-        android:id="@+id/menu_call_settings_call_log"
-        android:title="@string/call_settings"
-        android:icon="@drawable/ic_menu_settings_holo_light"
-        android:showAsAction="withText" />
 </menu>
diff --git a/res/menu/dialpad_options.xml b/res/menu/dialpad_options.xml
index 4dc62a8..77da9cb 100644
--- a/res/menu/dialpad_options.xml
+++ b/res/menu/dialpad_options.xml
@@ -30,10 +30,4 @@
         android:icon="@drawable/ic_menu_wait"
         android:title="@string/add_wait"
         android:showAsAction="withText" />
-
-    <item
-        android:id="@+id/menu_call_settings_dialpad"
-        android:title="@string/call_settings"
-        android:icon="@drawable/ic_menu_settings_holo_light"
-        android:showAsAction="withText" />
 </menu>
diff --git a/res/menu/dialtacts_options.xml b/res/menu/dialtacts_options.xml
index 99f87ff..cc9543a 100644
--- a/res/menu/dialtacts_options.xml
+++ b/res/menu/dialtacts_options.xml
@@ -20,6 +20,12 @@
         android:showAsAction="always" />
 
     <item
+        android:id="@+id/menu_call_settings"
+        android:title="@string/call_settings"
+        android:icon="@drawable/ic_menu_settings_holo_light"
+        android:showAsAction="withText" />
+
+    <item
         android:id="@+id/filter_option"
         android:title="@string/menu_contacts_filter"
         android:showAsAction="withText" />
diff --git a/res/values-sw580dp-w720dp/styles.xml b/res/values-sw580dp-w720dp/styles.xml
index 4314c80..e386bda 100644
--- a/res/values-sw580dp-w720dp/styles.xml
+++ b/res/values-sw580dp-w720dp/styles.xml
@@ -46,6 +46,7 @@
         <item name="contact_filter_popup_width">320dip</item>
         <item name="contact_browser_list_padding_left">0dip</item>
         <item name="contact_browser_list_padding_right">0dip</item>
+        <item name="contact_browser_background">@android:color/transparent</item>
         <item name="list_item_text_indent">@dimen/contact_browser_list_item_text_indent</item>
         <!-- Favorites -->
         <item name="favorites_padding_bottom">0dip</item>
diff --git a/res/values-sw580dp/styles.xml b/res/values-sw580dp/styles.xml
index efbbde2..9244faa 100644
--- a/res/values-sw580dp/styles.xml
+++ b/res/values-sw580dp/styles.xml
@@ -46,6 +46,7 @@
         <item name="contact_filter_popup_width">320dip</item>
         <item name="contact_browser_list_padding_left">0dip</item>
         <item name="contact_browser_list_padding_right">0dip</item>
+        <item name="contact_browser_background">@android:color/transparent</item>
         <item name="list_item_text_indent">@dimen/contact_browser_list_item_text_indent</item>
         <!-- Favorites -->
         <item name="favorites_padding_bottom">0dip</item>
@@ -77,6 +78,7 @@
         <item name="contact_filter_popup_width">320dip</item>
         <item name="contact_browser_list_padding_left">16dip</item>
         <item name="contact_browser_list_padding_right">0dip</item>
+        <item name="contact_browser_background">@android:color/transparent</item>
     </style>
     <style name="ContactPickerLayout" parent="ContactPickerTheme">
         <item name="android:layout_width">match_parent</item>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 7bec43a..cd5d137 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -70,6 +70,7 @@
         <attr name="contact_filter_popup_width" format="dimension"/>
         <attr name="contact_browser_list_padding_left" format="dimension"/>
         <attr name="contact_browser_list_padding_right" format="dimension"/>
+        <attr name="contact_browser_background" format="reference"/>
     </declare-styleable>
 
     <declare-styleable name="ContactListItemView">
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 1e47948..64d7dfb 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -95,7 +95,10 @@
     <!-- Color of the text for buttons in the action bar  -->
     <color name="action_bar_button_text_color">#FFFFFF</color>
 
-    <!-- Color of the vertical and horizontal dividers for starred contacts in the Phone app -->
-    <color name="phone_contact_tile_divider_color">#D8000000</color>
+    <!--  Color of the status message for starred contacts in the People app -->
+    <color name="people_contact_tile_status_color">#CCCCCC</color>
+
+    <!--  Color of the semi-transparent shadow box on contact tiles -->
+    <color name="contact_tile_shadow_box_color">#7F000000</color>
 
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 81b962f..2f74c32 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -255,4 +255,7 @@
     <!-- Height for directory headers in contact lists -->
     <dimen name="directory_header_height">24dip</dimen>
 
+    <!--  Vertical and horizontal padding in between contact tiles -->
+    <dimen name="contact_tile_divider_padding">2dip</dimen>
+
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 84c3d15..ae74a56 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -47,6 +47,7 @@
         <item name="contact_filter_popup_width">320dip</item>
         <item name="contact_browser_list_padding_left">16dip</item>
         <item name="contact_browser_list_padding_right">0dip</item>
+        <item name="contact_browser_background">@android:color/black</item>
         <item name="list_item_text_indent">@dimen/contact_browser_list_item_text_indent</item>
         <!-- CallLog -->
         <item name="call_log_primary_text_color">#FFFFFF</item>
@@ -156,6 +157,7 @@
         <item name="contact_filter_popup_width">320dip</item>
         <item name="contact_browser_list_padding_left">16dip</item>
         <item name="contact_browser_list_padding_right">0dip</item>
+        <item name="contact_browser_background">@android:color/transparent</item>
         <item name="list_item_text_indent">@dimen/contact_browser_list_item_text_indent</item>
         <item name="list_item_contacts_count_text_size">12sp</item>
         <!-- Favorites -->
@@ -199,6 +201,7 @@
         <item name="contact_filter_popup_width">320dip</item>
         <item name="contact_browser_list_padding_left">16dip</item>
         <item name="contact_browser_list_padding_right">0dip</item>
+        <item name="contact_browser_background">@android:color/transparent</item>
     </style>
 
     <style name="ContactPickerTheme" parent="@style/PeopleTheme">
@@ -270,25 +273,14 @@
         <item name="android:soundEffectsEnabled">false</item>
     </style>
 
-    <style name="ContactTileStarred">
-        <item name="android:focusable">true</item>
+    <style name="FavoritesFragmentStyle">
+        <item name="android:layout_width">0dip</item>
+        <item name="android:layout_weight">1</item>
     </style>
 
-    <style name="ContactTileStarredName">
-        <item name="android:singleLine">true</item>
-        <item name="android:ellipsize">end</item>
-        <item name="android:textSize">16sp</item>
-    </style>
-
-    <style name="ContactTileStatusText">
-        <item name="android:singleLine">true</item>
-        <item name="android:ellipsize">end</item>
-        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
-        <item name="android:textColor">#777777</item>
-    </style>
-
-    <style name="ContactTileStarredShadowBox">
-        <item name="android:background">#7F000000</item>
+    <style name="FrequentFragmentStyle">
+        <item name="android:layout_width">0dip</item>
+        <item name="android:layout_weight">1</item>
     </style>
 
     <style name="DialtactsActionBarStyle" parent="android:Widget.Holo.ActionBar">
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index ceaa246..dbfe411 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -23,6 +23,7 @@
 import com.android.contacts.util.StreamItemPhotoEntry;
 import com.google.android.collect.Lists;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Sets;
 
 import android.content.ContentResolver;
 import android.content.ContentUris;
@@ -62,6 +63,7 @@
 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.
@@ -76,7 +78,7 @@
     private Result mContact;
     private ForceLoadContentObserver mObserver;
     private boolean mDestroyed;
-
+    private final Set<Long> mNotifiedRawContactIds = Sets.newHashSet();
 
     public interface Listener {
         public void onContactLoaded(Result contact);
@@ -1115,14 +1117,18 @@
         Context context = getContext();
         for (Entity entity : mContact.getEntities()) {
             final ContentValues entityValues = entity.getEntityValues();
+            final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
+            if (mNotifiedRawContactIds.contains(rawContactId)) {
+                continue; // Already notified for this raw contact.
+            }
+            mNotifiedRawContactIds.add(rawContactId);
             final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
             final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
-            final AccountType accountType = AccountTypeManager.getInstance(context ).getAccountType(
+            final AccountType accountType = AccountTypeManager.getInstance(context).getAccountType(
                     type, dataSet);
             final String serviceName = accountType.getViewContactNotifyServiceClassName();
             final String resPackageName = accountType.resPackageName;
             if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(resPackageName)) {
-                final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
                 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
                 final Intent intent = new Intent();
                 intent.setClassName(resPackageName, serviceName);
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 3bb330f..78c4b18 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -53,8 +53,8 @@
 
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * A service responsible for saving changes to the content provider.
@@ -133,7 +133,8 @@
         public void onServiceCompleted(Intent callbackIntent);
     }
 
-    private static final LinkedList<Listener> sListeners = new LinkedList<Listener>();
+    private static final CopyOnWriteArrayList<Listener> sListeners =
+            new CopyOnWriteArrayList<Listener>();
 
     private Handler mMainHandler;
 
@@ -148,15 +149,11 @@
             throw new ClassCastException("Only activities can be registered to"
                     + " receive callback from " + ContactSaveService.class.getName());
         }
-        synchronized (sListeners) {
-            sListeners.addFirst(listener);
-        }
+        sListeners.add(0, listener);
     }
 
     public static void unregisterListener(Listener listener) {
-        synchronized (sListeners) {
-            sListeners.remove(listener);
-        }
+        sListeners.remove(listener);
     }
 
     @Override
@@ -975,13 +972,11 @@
         // TODO: this assumes that if there are multiple instances of the same
         // activity registered, the last one registered is the one waiting for
         // the callback. Validity of this assumption needs to be verified.
-        synchronized (sListeners) {
-            for (Listener listener : sListeners) {
-                if (callbackIntent.getComponent().equals(
-                        ((Activity) listener).getIntent().getComponent())) {
-                    listener.onServiceCompleted(callbackIntent);
-                    return;
-                }
+        for (Listener listener : sListeners) {
+            if (callbackIntent.getComponent().equals(
+                    ((Activity) listener).getIntent().getComponent())) {
+                listener.onServiceCompleted(callbackIntent);
+                return;
             }
         }
     }
diff --git a/src/com/android/contacts/ContactsUtils.java b/src/com/android/contacts/ContactsUtils.java
index 76cbc7d..45ce4fe 100644
--- a/src/com/android/contacts/ContactsUtils.java
+++ b/src/com/android/contacts/ContactsUtils.java
@@ -16,10 +16,10 @@
 
 package com.android.contacts;
 
-import com.google.i18n.phonenumbers.NumberParseException;
-import com.google.i18n.phonenumbers.PhoneNumberUtil;
-import com.google.i18n.phonenumbers.PhoneNumberUtil.MatchType;
-import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import com.android.i18n.phonenumbers.NumberParseException;
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
+import com.android.i18n.phonenumbers.PhoneNumberUtil.MatchType;
+import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
 
 import android.content.Context;
 import android.content.Intent;
diff --git a/src/com/android/contacts/GroupListLoader.java b/src/com/android/contacts/GroupListLoader.java
index b567ba8..2589c9b 100644
--- a/src/com/android/contacts/GroupListLoader.java
+++ b/src/com/android/contacts/GroupListLoader.java
@@ -34,8 +34,6 @@
         Groups.DATA_SET,
         Groups._ID,
         Groups.TITLE,
-        Groups.ACTION,
-        Groups.ACTION_URI,
         Groups.SUMMARY_COUNT,
     };
 
@@ -44,9 +42,7 @@
     public final static int DATA_SET = 2;
     public final static int GROUP_ID = 3;
     public final static int TITLE = 4;
-    public final static int ACTION = 5;
-    public final static int ACTION_URI = 6;
-    public final static int MEMBER_COUNT = 7;
+    public final static int MEMBER_COUNT = 5;
 
     private static final Uri GROUP_LIST_URI = Groups.CONTENT_SUMMARY_URI;
 
diff --git a/src/com/android/contacts/GroupMetaDataLoader.java b/src/com/android/contacts/GroupMetaDataLoader.java
index 2f3468b..8344041 100644
--- a/src/com/android/contacts/GroupMetaDataLoader.java
+++ b/src/com/android/contacts/GroupMetaDataLoader.java
@@ -36,8 +36,6 @@
         Groups.FAVORITES,
         Groups.GROUP_IS_READ_ONLY,
         Groups.DELETED,
-        Groups.ACTION,
-        Groups.ACTION_URI,
     };
 
     public final static int ACCOUNT_NAME = 0;
@@ -49,8 +47,6 @@
     public final static int FAVORITES = 6;
     public final static int IS_READ_ONLY = 7;
     public final static int DELETED = 8;
-    public final static int ACTION = 9;
-    public final static int ACTION_URI = 10;
 
     public GroupMetaDataLoader(Context context, Uri groupUri) {
         super(context, ensureIsGroupUri(groupUri), COLUMNS, Groups.ACCOUNT_TYPE + " NOT NULL AND "
diff --git a/src/com/android/contacts/PhoneCallDetailsHelper.java b/src/com/android/contacts/PhoneCallDetailsHelper.java
index e970fcc..e79bdce 100644
--- a/src/com/android/contacts/PhoneCallDetailsHelper.java
+++ b/src/com/android/contacts/PhoneCallDetailsHelper.java
@@ -107,7 +107,8 @@
             mPhoneNumberHelper.getDisplayNumber(details.number, details.formattedNumber);
         if (TextUtils.isEmpty(details.name)) {
             nameText = displayNumber;
-            if (TextUtils.isEmpty(details.geocode)) {
+            if (TextUtils.isEmpty(details.geocode)
+                    || mPhoneNumberHelper.isVoicemailNumber(details.number)) {
                 numberText = mResources.getString(R.string.call_log_empty_gecode);
             } else {
                 numberText = details.geocode;
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index 13ce317..d0acc6b 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -552,6 +552,7 @@
                 Log.e(TAG, "DialpadFragment isn't ready yet when the tab is already selected.");
             }
         }
+        invalidateOptionsMenu();
     }
 
     /** Returns true if the given intent contains a phone number to populate the dialer with */
@@ -643,19 +644,24 @@
     public boolean onPrepareOptionsMenu(Menu menu) {
         final MenuItem searchMenuItem = menu.findItem(R.id.search_on_action_bar);
         final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
+        final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings);
         Tab tab = getActionBar().getSelectedTab();
         if (mInSearchUi) {
             searchMenuItem.setVisible(false);
             filterOptionMenuItem.setVisible(true);
             filterOptionMenuItem.setOnMenuItemClickListener(
                     mFilterOptionsMenuItemClickListener);
-        } else if (tab == null || tab.getPosition() == TAB_INDEX_DIALER) {
-            searchMenuItem.setVisible(false);
-            filterOptionMenuItem.setVisible(false);
+            callSettingsMenuItem.setVisible(false);
         } else {
+            if (tab != null && tab.getPosition() == TAB_INDEX_DIALER) {
+                searchMenuItem.setVisible(false);
+            } else {
+                searchMenuItem.setVisible(true);
+                searchMenuItem.setOnMenuItemClickListener(mSearchMenuItemClickListener);
+            }
             filterOptionMenuItem.setVisible(false);
-            searchMenuItem.setVisible(true);
-            searchMenuItem.setOnMenuItemClickListener(mSearchMenuItemClickListener);
+            callSettingsMenuItem.setVisible(true);
+            callSettingsMenuItem.setIntent(DialtactsActivity.getCallSettingsIntent());
         }
 
         return true;
@@ -753,7 +759,7 @@
 
     private void hideInputMethod(View view) {
         InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
-        if (imm != null) {
+        if (imm != null && view != null) {
             imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
         }
     }
diff --git a/src/com/android/contacts/activities/GroupDetailActivity.java b/src/com/android/contacts/activities/GroupDetailActivity.java
index b66d8b8..47e02a7 100644
--- a/src/com/android/contacts/activities/GroupDetailActivity.java
+++ b/src/com/android/contacts/activities/GroupDetailActivity.java
@@ -20,11 +20,15 @@
 import com.android.contacts.R;
 import com.android.contacts.group.GroupDetailDisplayUtils;
 import com.android.contacts.group.GroupDetailFragment;
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
 
 import android.app.ActionBar;
+import android.content.ContentUris;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.provider.ContactsContract.Groups;
 import android.text.TextUtils;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -40,8 +44,8 @@
 
     private String mAccountTypeString;
     private String mDataSet;
-    private String mGroupSourceAction;
-    private String mGroupSourceUri;
+
+    private GroupDetailFragment mFragment;
 
     @Override
     public void onCreate(Bundle savedState) {
@@ -55,12 +59,12 @@
         mShowGroupSourceInActionBar = getResources().getBoolean(
                 R.bool.config_show_group_action_in_action_bar);
 
-        GroupDetailFragment fragment = (GroupDetailFragment) getFragmentManager().findFragmentById(
+        mFragment = (GroupDetailFragment) getFragmentManager().findFragmentById(
                 R.id.group_detail_fragment);
-        fragment.setListener(mFragmentListener);
-        fragment.setShowGroupSourceInActionBar(mShowGroupSourceInActionBar);
-        fragment.loadGroup(getIntent().getData());
-        fragment.closeActivityAfterDelete(true);
+        mFragment.setListener(mFragmentListener);
+        mFragment.setShowGroupSourceInActionBar(mShowGroupSourceInActionBar);
+        mFragment.loadGroup(getIntent().getData());
+        mFragment.closeActivityAfterDelete(true);
 
         // We want the UP affordance but no app icon.
         ActionBar actionBar = getActionBar();
@@ -85,12 +89,9 @@
         }
 
         @Override
-        public void onGroupSourceUpdated(String accountTypeString, String dataSet,
-                String groupSourceAction, String groupSourceActionUri) {
+        public void onAccountTypeUpdated(String accountTypeString, String dataSet) {
             mAccountTypeString = accountTypeString;
             mDataSet = dataSet;
-            mGroupSourceAction = groupSourceAction;
-            mGroupSourceUri = groupSourceActionUri;
             invalidateOptionsMenu();
         }
 
@@ -128,8 +129,11 @@
         if (groupSourceMenuItem == null) {
             return false;
         }
-        if (TextUtils.isEmpty(mAccountTypeString) || TextUtils.isEmpty(mGroupSourceAction) ||
-                TextUtils.isEmpty(mGroupSourceUri)) {
+        final AccountTypeManager manager = AccountTypeManager.getInstance(this);
+        final AccountType accountType =
+                manager.getAccountType(mAccountTypeString, mDataSet);
+        if (TextUtils.isEmpty(mAccountTypeString)
+                || TextUtils.isEmpty(accountType.getViewGroupActivity())) {
             groupSourceMenuItem.setVisible(false);
             return false;
         }
@@ -139,7 +143,11 @@
         groupSourceView.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
-                startActivity(new Intent(mGroupSourceAction, Uri.parse(mGroupSourceUri)));
+                final Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI,
+                        mFragment.getGroupId());
+                final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+                intent.setClassName(accountType.resPackageName, accountType.getViewGroupActivity());
+                startActivity(intent);
             }
         });
         groupSourceMenuItem.setActionView(groupSourceView);
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index cab8afd..1da1c97 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -23,7 +23,6 @@
 import com.android.contacts.activities.ActionBarAdapter.TabState;
 import com.android.contacts.detail.ContactDetailFragment;
 import com.android.contacts.detail.ContactDetailLayoutController;
-import com.android.contacts.detail.ContactDetailTabCarousel;
 import com.android.contacts.detail.ContactDetailUpdatesFragment;
 import com.android.contacts.detail.ContactLoaderFragment;
 import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener;
@@ -39,6 +38,7 @@
 import com.android.contacts.list.ContactListFilter;
 import com.android.contacts.list.ContactListFilterController;
 import com.android.contacts.list.ContactTileAdapter.DisplayType;
+import com.android.contacts.list.ContactTileListFragment;
 import com.android.contacts.list.ContactsIntentResolver;
 import com.android.contacts.list.ContactsRequest;
 import com.android.contacts.list.ContactsUnavailableFragment;
@@ -48,7 +48,6 @@
 import com.android.contacts.list.OnContactsUnavailableActionListener;
 import com.android.contacts.list.ProviderStatusLoader;
 import com.android.contacts.list.ProviderStatusLoader.ProviderStatusListener;
-import com.android.contacts.list.ContactTileListFragment;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.AccountWithDataSet;
 import com.android.contacts.preference.ContactsPreferenceActivity;
@@ -1160,8 +1159,7 @@
         }
 
         @Override
-        public void onGroupSourceUpdated(String accountTypeString, String dataSet,
-                String groupSourceAction, String groupSourceUri) {
+        public void onAccountTypeUpdated(String accountTypeString, String dataSet) {
             // Nothing needs to be done here because the group source will be displayed in the
             // detail fragment
         }
diff --git a/src/com/android/contacts/calllog/CallLogAdapter.java b/src/com/android/contacts/calllog/CallLogAdapter.java
new file mode 100644
index 0000000..7e934b6
--- /dev/null
+++ b/src/com/android/contacts/calllog/CallLogAdapter.java
@@ -0,0 +1,755 @@
+/*
+ * 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.calllog;
+
+import com.android.common.widget.GroupingListAdapter;
+import com.android.contacts.ContactPhotoManager;
+import com.android.contacts.PhoneCallDetails;
+import com.android.contacts.PhoneCallDetailsHelper;
+import com.android.contacts.R;
+import com.android.contacts.util.ExpirableCache;
+import com.google.common.annotations.VisibleForTesting;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.PhoneLookup;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+
+import java.util.LinkedList;
+
+/**
+ * Adapter class to fill in data for the Call Log.
+ */
+public final class CallLogAdapter extends GroupingListAdapter
+        implements Runnable, ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator {
+    /** Interface used to initiate a refresh of the content. */
+    public interface CallFetcher {
+        public void startCallsQuery();
+    }
+
+    /** The time in millis to delay starting the thread processing requests. */
+    private static final int START_PROCESSING_REQUESTS_DELAY_MILLIS = 1000;
+
+    /** The size of the cache of contact info. */
+    private static final int CONTACT_INFO_CACHE_SIZE = 100;
+
+    private final Context mContext;
+    private final String mCurrentCountryIso;
+    private final CallFetcher mCallFetcher;
+
+    /**
+     * A cache of the contact details for the phone numbers in the call log.
+     * <p>
+     * The content of the cache is expired (but not purged) whenever the application comes to
+     * the foreground.
+     */
+    private ExpirableCache<String, ContactInfo> mContactInfoCache;
+
+    /**
+     * List of requests to update contact details.
+     * <p>
+     * The requests are added when displaying the contacts and are processed by a background
+     * thread.
+     */
+    private final LinkedList<String> mRequests;
+
+    private volatile boolean mDone;
+    private boolean mLoading = true;
+    private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
+    private static final int REDRAW = 1;
+    private static final int START_THREAD = 2;
+    private boolean mFirst;
+    private Thread mCallerIdThread;
+
+    /** Instance of helper class for managing views. */
+    private final CallLogListItemHelper mCallLogViewsHelper;
+
+    /** Helper to set up contact photos. */
+    private final ContactPhotoManager mContactPhotoManager;
+    /** Helper to parse and process phone numbers. */
+    private PhoneNumberHelper mPhoneNumberHelper;
+    /** Helper to group call log entries. */
+    private final CallLogGroupBuilder mCallLogGroupBuilder;
+
+    /** Can be set to true by tests to disable processing of requests. */
+    private volatile boolean mRequestProcessingDisabled = false;
+
+    /** Listener for the primary action in the list, opens the call details. */
+    private final View.OnClickListener mPrimaryActionListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            IntentProvider intentProvider = (IntentProvider) view.getTag();
+            if (intentProvider != null) {
+                mContext.startActivity(intentProvider.getIntent(mContext));
+            }
+        }
+    };
+    /** Listener for the secondary action in the list, either call or play. */
+    private final View.OnClickListener mSecondaryActionListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            IntentProvider intentProvider = (IntentProvider) view.getTag();
+            if (intentProvider != null) {
+                mContext.startActivity(intentProvider.getIntent(mContext));
+            }
+        }
+    };
+
+    @Override
+    public boolean onPreDraw() {
+        if (mFirst) {
+            mHandler.sendEmptyMessageDelayed(START_THREAD,
+                    START_PROCESSING_REQUESTS_DELAY_MILLIS);
+            mFirst = false;
+        }
+        return true;
+    }
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case REDRAW:
+                    notifyDataSetChanged();
+                    break;
+                case START_THREAD:
+                    startRequestProcessing();
+                    break;
+            }
+        }
+    };
+
+    public CallLogAdapter(Context context, CallFetcher callFetcher,
+            String currentCountryIso, String voicemailNumber) {
+        super(context);
+
+        mContext = context;
+        mCurrentCountryIso = currentCountryIso;
+        mCallFetcher = callFetcher;
+
+        mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
+        mRequests = new LinkedList<String>();
+        mPreDrawListener = null;
+
+        Resources resources = mContext.getResources();
+        CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
+
+        mContactPhotoManager = ContactPhotoManager.getInstance(mContext);
+        mPhoneNumberHelper = new PhoneNumberHelper(resources, voicemailNumber);
+        PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(
+                resources, callTypeHelper, mPhoneNumberHelper);
+        mCallLogViewsHelper =
+                new CallLogListItemHelper(
+                        phoneCallDetailsHelper, mPhoneNumberHelper, resources);
+        mCallLogGroupBuilder = new CallLogGroupBuilder(this);
+    }
+
+    /**
+     * Requery on background thread when {@link Cursor} changes.
+     */
+    @Override
+    protected void onContentChanged() {
+        // When the content changes, always fetch all the calls, in case a new missed call came
+        // in and we were filtering over voicemail only, so that we see the missed call.
+        mCallFetcher.startCallsQuery();
+    }
+
+    void setLoading(boolean loading) {
+        mLoading = loading;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        if (mLoading) {
+            // We don't want the empty state to show when loading.
+            return false;
+        } else {
+            return super.isEmpty();
+        }
+    }
+
+    public ContactInfo getContactInfo(String number) {
+        return mContactInfoCache.getPossiblyExpired(number);
+    }
+
+    public void startRequestProcessing() {
+        if (mRequestProcessingDisabled) {
+            return;
+        }
+
+        mDone = false;
+        mCallerIdThread = new Thread(this, "CallLogContactLookup");
+        mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
+        mCallerIdThread.start();
+    }
+
+    /**
+     * Stops the background thread that processes updates and cancels any pending requests to
+     * start it.
+     * <p>
+     * Should be called from the main thread to prevent a race condition between the request to
+     * start the thread being processed and stopping the thread.
+     */
+    public void stopRequestProcessing() {
+        // Remove any pending requests to start the processing thread.
+        mHandler.removeMessages(START_THREAD);
+        mDone = true;
+        if (mCallerIdThread != null) mCallerIdThread.interrupt();
+    }
+
+    public void invalidateCache() {
+        mContactInfoCache.expireAll();
+        // Let it restart the thread after next draw
+        mPreDrawListener = null;
+    }
+
+    private void enqueueRequest(String number, boolean immediate) {
+        synchronized (mRequests) {
+            if (!mRequests.contains(number)) {
+                mRequests.add(number);
+                mRequests.notifyAll();
+            }
+        }
+        if (mFirst && immediate) {
+            startRequestProcessing();
+            mFirst = false;
+        }
+    }
+
+    /**
+     * Determines the contact information for the given SIP address.
+     * <p>
+     * It returns the contact info if found.
+     * <p>
+     * If no contact corresponds to the given SIP address, returns {@link ContactInfo#EMPTY}.
+     * <p>
+     * If the lookup fails for some other reason, it returns null.
+     */
+    private ContactInfo queryContactInfoForSipAddress(String sipAddress) {
+        final ContactInfo info;
+
+        // TODO: This code is duplicated from the
+        // CallerInfoAsyncQuery class.  To avoid that, could the
+        // code here just use CallerInfoAsyncQuery, rather than
+        // manually running ContentResolver.query() itself?
+
+        // We look up SIP addresses directly in the Data table:
+        Uri contactRef = Data.CONTENT_URI;
+
+        // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+        //
+        // Also note we use "upper(data1)" in the WHERE clause, and
+        // uppercase the incoming SIP address, in order to do a
+        // case-insensitive match.
+        //
+        // TODO: May also need to normalize by adding "sip:" as a
+        // prefix, if we start storing SIP addresses that way in the
+        // database.
+        String selection = "upper(" + Data.DATA1 + ")=?"
+                + " AND "
+                + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
+        String[] selectionArgs = new String[] { sipAddress.toUpperCase() };
+
+        Cursor dataTableCursor =
+                mContext.getContentResolver().query(
+                        contactRef,
+                        null,  // projection
+                        selection,  // selection
+                        selectionArgs,  // selectionArgs
+                        null);  // sortOrder
+
+        if (dataTableCursor != null) {
+            if (dataTableCursor.moveToFirst()) {
+                info = new ContactInfo();
+
+                // TODO: we could slightly speed this up using an
+                // explicit projection (and thus not have to do
+                // those getColumnIndex() calls) but the benefit is
+                // very minimal.
+
+                // Note the Data.CONTACT_ID column here is
+                // equivalent to the PERSON_ID_COLUMN_INDEX column
+                // we use with "phonesCursor" below.
+                info.personId = dataTableCursor.getLong(
+                        dataTableCursor.getColumnIndex(Data.CONTACT_ID));
+                info.name = dataTableCursor.getString(
+                        dataTableCursor.getColumnIndex(Data.DISPLAY_NAME));
+                // "type" and "label" are currently unused for SIP addresses
+                info.type = SipAddress.TYPE_OTHER;
+                info.label = null;
+
+                // And "number" is the SIP address.
+                // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+                info.number = dataTableCursor.getString(
+                        dataTableCursor.getColumnIndex(Data.DATA1));
+                info.normalizedNumber = null;  // meaningless for SIP addresses
+                final String thumbnailUriString = dataTableCursor.getString(
+                        dataTableCursor.getColumnIndex(Data.PHOTO_THUMBNAIL_URI));
+                info.thumbnailUri = thumbnailUriString == null
+                        ? null
+                        : Uri.parse(thumbnailUriString);
+                info.lookupKey = dataTableCursor.getString(
+                        dataTableCursor.getColumnIndex(Data.LOOKUP_KEY));
+            } else {
+                info = ContactInfo.EMPTY;
+            }
+            dataTableCursor.close();
+        } else {
+            // Failed to fetch the data, ignore this request.
+            info = null;
+        }
+        return info;
+    }
+
+    /**
+     * Determines the contact information for the given phone number.
+     * <p>
+     * It returns the contact info if found.
+     * <p>
+     * If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
+     * <p>
+     * If the lookup fails for some other reason, it returns null.
+     */
+    private ContactInfo queryContactInfoForPhoneNumber(String number) {
+        final ContactInfo info;
+
+        // "number" is a regular phone number, so use the
+        // PhoneLookup table:
+        Cursor phonesCursor =
+                mContext.getContentResolver().query(
+                    Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+                            Uri.encode(number)),
+                            PhoneQuery._PROJECTION, null, null, null);
+        if (phonesCursor != null) {
+            if (phonesCursor.moveToFirst()) {
+                info = new ContactInfo();
+                info.personId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
+                info.name = phonesCursor.getString(PhoneQuery.NAME);
+                info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
+                info.label = phonesCursor.getString(PhoneQuery.LABEL);
+                info.number = phonesCursor
+                        .getString(PhoneQuery.MATCHED_NUMBER);
+                info.normalizedNumber = phonesCursor
+                        .getString(PhoneQuery.NORMALIZED_NUMBER);
+                final String thumbnailUriString = phonesCursor.getString(
+                        PhoneQuery.THUMBNAIL_URI);
+                info.thumbnailUri = thumbnailUriString == null
+                        ? null
+                        : Uri.parse(thumbnailUriString);
+                info.lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
+            } else {
+                info = ContactInfo.EMPTY;
+            }
+            phonesCursor.close();
+        } else {
+            // Failed to fetch the data, ignore this request.
+            info = null;
+        }
+        return info;
+    }
+
+    /**
+     * Queries the appropriate content provider for the contact associated with the number.
+     * <p>
+     * The number might be either a SIP address or a phone number.
+     * <p>
+     * It returns true if it updated the content of the cache and we should therefore tell the
+     * view to update its content.
+     */
+    private boolean queryContactInfo(String number) {
+        final ContactInfo info;
+
+        // Determine the contact info.
+        if (PhoneNumberUtils.isUriNumber(number)) {
+            // This "number" is really a SIP address.
+            info = queryContactInfoForSipAddress(number);
+        } else {
+            info = queryContactInfoForPhoneNumber(number);
+        }
+
+        if (info == null) {
+            // The lookup failed, just return without requesting to update the view.
+            return false;
+        }
+
+        // Check the existing entry in the cache: only if it has changed we should update the
+        // view.
+        ContactInfo existingInfo = mContactInfoCache.getPossiblyExpired(number);
+        boolean updated = !info.equals(existingInfo);
+        if (updated) {
+            // The formattedNumber is computed by the UI thread when needed. Since we updated
+            // the details of the contact, set this value to null for now.
+            info.formattedNumber = null;
+        }
+        // Store the data in the cache so that the UI thread can use to display it. Store it
+        // even if it has not changed so that it is marked as not expired.
+        mContactInfoCache.put(number, info);
+        return updated;
+    }
+
+    /*
+     * Handles requests for contact name and number type
+     * @see java.lang.Runnable#run()
+     */
+    @Override
+    public void run() {
+        boolean needNotify = false;
+        while (!mDone) {
+            String number = null;
+            synchronized (mRequests) {
+                if (!mRequests.isEmpty()) {
+                    number = mRequests.removeFirst();
+                } else {
+                    if (needNotify) {
+                        needNotify = false;
+                        mHandler.sendEmptyMessage(REDRAW);
+                    }
+                    try {
+                        mRequests.wait(1000);
+                    } catch (InterruptedException ie) {
+                        // Ignore and continue processing requests
+                        Thread.currentThread().interrupt();
+                    }
+                }
+            }
+            if (!mDone && number != null && queryContactInfo(number)) {
+                needNotify = true;
+            }
+        }
+    }
+
+    @Override
+    protected void addGroups(Cursor cursor) {
+        mCallLogGroupBuilder.addGroups(cursor);
+    }
+
+    @VisibleForTesting
+    @Override
+    public View newStandAloneView(Context context, ViewGroup parent) {
+        LayoutInflater inflater =
+                (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
+        findAndCacheViews(view);
+        return view;
+    }
+
+    @VisibleForTesting
+    @Override
+    public void bindStandAloneView(View view, Context context, Cursor cursor) {
+        bindView(view, cursor, 1);
+    }
+
+    @VisibleForTesting
+    @Override
+    public View newChildView(Context context, ViewGroup parent) {
+        LayoutInflater inflater =
+                (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
+        findAndCacheViews(view);
+        return view;
+    }
+
+    @VisibleForTesting
+    @Override
+    public void bindChildView(View view, Context context, Cursor cursor) {
+        bindView(view, cursor, 1);
+    }
+
+    @VisibleForTesting
+    @Override
+    public View newGroupView(Context context, ViewGroup parent) {
+        LayoutInflater inflater =
+                (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
+        findAndCacheViews(view);
+        return view;
+    }
+
+    @VisibleForTesting
+    @Override
+    public void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
+            boolean expanded) {
+        bindView(view, cursor, groupSize);
+    }
+
+    private void findAndCacheViews(View view) {
+        // Get the views to bind to.
+        CallLogListItemViews views = CallLogListItemViews.fromView(view);
+        views.primaryActionView.setOnClickListener(mPrimaryActionListener);
+        views.secondaryActionView.setOnClickListener(mSecondaryActionListener);
+        view.setTag(views);
+    }
+
+    /**
+     * Binds the views in the entry to the data in the call log.
+     *
+     * @param view the view corresponding to this entry
+     * @param c the cursor pointing to the entry in the call log
+     * @param count the number of entries in the current item, greater than 1 if it is a group
+     */
+    private void bindView(View view, Cursor c, int count) {
+        final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+        final int section = c.getInt(CallLogQuery.SECTION);
+
+        // This might be a header: check the value of the section column in the cursor.
+        if (section == CallLogQuery.SECTION_NEW_HEADER
+                || section == CallLogQuery.SECTION_OLD_HEADER) {
+            views.listItemView.setVisibility(View.GONE);
+            views.listHeaderView.setVisibility(View.VISIBLE);
+            views.listHeaderTextView.setText(
+                    section == CallLogQuery.SECTION_NEW_HEADER
+                            ? R.string.call_log_new_header
+                            : R.string.call_log_old_header);
+            // Nothing else to set up for a header.
+            return;
+        }
+        // Default case: an item in the call log.
+        views.listItemView.setVisibility(View.VISIBLE);
+        views.listHeaderView.setVisibility(View.GONE);
+
+        final String number = c.getString(CallLogQuery.NUMBER);
+        final long date = c.getLong(CallLogQuery.DATE);
+        final long duration = c.getLong(CallLogQuery.DURATION);
+        final int callType = c.getInt(CallLogQuery.CALL_TYPE);
+        final String formattedNumber;
+        final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
+
+        final ContactInfo cachedContactInfo = getContactInfoFromCallLog(c);
+
+        views.primaryActionView.setTag(
+                IntentProvider.getCallDetailIntentProvider(
+                        this, c.getPosition(), c.getLong(CallLogQuery.ID), count));
+        // Store away the voicemail information so we can play it directly.
+        if (callType == Calls.VOICEMAIL_TYPE) {
+            String voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
+            final long rowId = c.getLong(CallLogQuery.ID);
+            views.secondaryActionView.setTag(
+                    IntentProvider.getPlayVoicemailIntentProvider(rowId, voicemailUri));
+        } else if (!TextUtils.isEmpty(number)) {
+            // Store away the number so we can call it directly if you click on the call icon.
+            views.secondaryActionView.setTag(
+                    IntentProvider.getReturnCallIntentProvider(number));
+        } else {
+            // No action enabled.
+            views.secondaryActionView.setTag(null);
+        }
+
+        // Lookup contacts with this number
+        ExpirableCache.CachedValue<ContactInfo> cachedInfo =
+                mContactInfoCache.getCachedValue(number);
+        ContactInfo info = cachedInfo == null ? null : cachedInfo.getValue();
+        if (cachedInfo == null) {
+            // Mark it as empty and queue up a request to find the name.
+            // The db request should happen on a non-UI thread.
+            info = ContactInfo.EMPTY;
+            mContactInfoCache.put(number, info);
+            // Request the contact details immediately since they are currently missing.
+            enqueueRequest(number, true);
+            // Format the phone number in the call log as best as we can.
+            formattedNumber = formatPhoneNumber(number, null, countryIso);
+        } else {
+            if (cachedInfo.isExpired()) {
+                // The contact info is no longer up to date, we should request it. However, we
+                // do not need to request them immediately.
+                enqueueRequest(number, false);
+            }
+
+            if (info != ContactInfo.EMPTY) {
+                // Format and cache phone number for found contact.
+                if (info.formattedNumber == null) {
+                    info.formattedNumber =
+                            formatPhoneNumber(info.number, info.normalizedNumber, countryIso);
+                }
+                formattedNumber = info.formattedNumber;
+            } else {
+                // Format the phone number in the call log as best as we can.
+                formattedNumber = formatPhoneNumber(number, null, countryIso);
+            }
+        }
+
+        if (info == null || info == ContactInfo.EMPTY) {
+            info = cachedContactInfo;
+        }
+
+        final long personId = info.personId;
+        final String name = info.name;
+        final int ntype = info.type;
+        final String label = info.label;
+        final Uri thumbnailUri = info.thumbnailUri;
+        final String lookupKey = info.lookupKey;
+        final int[] callTypes = getCallTypes(c, count);
+        final String geocode = c.getString(CallLogQuery.GEOCODED_LOCATION);
+        final PhoneCallDetails details;
+        if (TextUtils.isEmpty(name)) {
+            details = new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
+                    callTypes, date, duration);
+        } else {
+            details = new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
+                    callTypes, date, duration, name, ntype, label, personId, thumbnailUri);
+        }
+
+        final boolean isNew = CallLogQuery.isNewSection(c);
+        // New items also use the highlighted version of the text.
+        final boolean isHighlighted = isNew;
+        mCallLogViewsHelper.setPhoneCallDetails(views, details, isHighlighted);
+        setPhoto(views, thumbnailUri, personId, lookupKey);
+
+        // Listen for the first draw
+        if (mPreDrawListener == null) {
+            mFirst = true;
+            mPreDrawListener = this;
+            view.getViewTreeObserver().addOnPreDrawListener(this);
+        }
+    }
+
+    /** Returns the contact information as stored in the call log. */
+    private ContactInfo getContactInfoFromCallLog(Cursor c) {
+        ContactInfo info = new ContactInfo();
+        info.personId = -1;
+        info.name = c.getString(CallLogQuery.CACHED_NAME);
+        info.type = c.getInt(CallLogQuery.CACHED_NUMBER_TYPE);
+        info.label = c.getString(CallLogQuery.CACHED_NUMBER_LABEL);
+        // TODO: This should be added to the call log cached values.
+        info.number = c.getString(CallLogQuery.NUMBER);
+        info.formattedNumber = info.number;
+        info.normalizedNumber = info.number;
+        info.thumbnailUri = null;
+        info.lookupKey = null;
+        return info;
+    }
+
+    /**
+     * Returns the call types for the given number of items in the cursor.
+     * <p>
+     * It uses the next {@code count} rows in the cursor to extract the types.
+     * <p>
+     * It position in the cursor is unchanged by this function.
+     */
+    private int[] getCallTypes(Cursor cursor, int count) {
+        int position = cursor.getPosition();
+        int[] callTypes = new int[count];
+        for (int index = 0; index < count; ++index) {
+            callTypes[index] = cursor.getInt(CallLogQuery.CALL_TYPE);
+            cursor.moveToNext();
+        }
+        cursor.moveToPosition(position);
+        return callTypes;
+    }
+
+    private void setPhoto(CallLogListItemViews views, Uri thumbnailUri, long contactId,
+            String lookupKey) {
+        views.quickContactView.assignContactUri(contactId == -1 ? null :
+                Contacts.getLookupUri(contactId, lookupKey));
+        mContactPhotoManager.loadPhoto(views.quickContactView, thumbnailUri);
+    }
+
+    /**
+     * Sets whether processing of requests for contact details should be enabled.
+     * <p>
+     * This method should be called in tests to disable such processing of requests when not
+     * needed.
+     */
+    public void disableRequestProcessingForTest() {
+        mRequestProcessingDisabled = true;
+    }
+
+    public void injectContactInfoForTest(String number, ContactInfo contactInfo) {
+        mContactInfoCache.put(number, contactInfo);
+    }
+
+    @Override
+    public void addGroup(int cursorPosition, int size, boolean expanded) {
+        super.addGroup(cursorPosition, size, expanded);
+    }
+
+    /**
+     * Format the given phone number
+     *
+     * @param number the number to be formatted.
+     * @param normalizedNumber the normalized number of the given number.
+     * @param countryIso the ISO 3166-1 two letters country code, the country's
+     *        convention will be used to format the number if the normalized
+     *        phone is null.
+     *
+     * @return the formatted number, or the given number if it was formatted.
+     */
+    private String formatPhoneNumber(String number, String normalizedNumber,
+            String countryIso) {
+        if (TextUtils.isEmpty(number)) {
+            return "";
+        }
+        // If "number" is really a SIP address, don't try to do any formatting at all.
+        if (PhoneNumberUtils.isUriNumber(number)) {
+            return number;
+        }
+        if (TextUtils.isEmpty(countryIso)) {
+            countryIso = mCurrentCountryIso;
+        }
+        return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
+    }
+
+    /*
+     * Get the number from the Contacts, if available, since sometimes
+     * the number provided by caller id may not be formatted properly
+     * depending on the carrier (roaming) in use at the time of the
+     * incoming call.
+     * Logic : If the caller-id number starts with a "+", use it
+     *         Else if the number in the contacts starts with a "+", use that one
+     *         Else if the number in the contacts is longer, use that one
+     */
+    public String getBetterNumberFromContacts(String number) {
+        String matchingNumber = null;
+        // Look in the cache first. If it's not found then query the Phones db
+        ContactInfo ci = mContactInfoCache.getPossiblyExpired(number);
+        if (ci != null && ci != ContactInfo.EMPTY) {
+            matchingNumber = ci.number;
+        } else {
+            try {
+                Cursor phonesCursor = mContext.getContentResolver().query(
+                        Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, number),
+                        PhoneQuery._PROJECTION, null, null, null);
+                if (phonesCursor != null) {
+                    if (phonesCursor.moveToFirst()) {
+                        matchingNumber = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
+                    }
+                    phonesCursor.close();
+                }
+            } catch (Exception e) {
+                // Use the number from the call log
+            }
+        }
+        if (!TextUtils.isEmpty(matchingNumber) &&
+                (matchingNumber.startsWith("+")
+                        || matchingNumber.length() > number.length())) {
+            number = matchingNumber;
+        }
+        return number;
+    }
+}
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 2d0ddbd..215fd7b 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -16,40 +16,27 @@
 
 package com.android.contacts.calllog;
 
-import com.android.common.widget.GroupingListAdapter;
-import com.android.contacts.ContactPhotoManager;
 import com.android.contacts.ContactsUtils;
-import com.android.contacts.PhoneCallDetails;
-import com.android.contacts.PhoneCallDetailsHelper;
 import com.android.contacts.R;
 import com.android.contacts.activities.DialtactsActivity;
 import com.android.contacts.activities.DialtactsActivity.ViewPagerVisibilityListener;
 import com.android.contacts.test.NeededForTesting;
-import com.android.contacts.util.ExpirableCache;
 import com.android.contacts.voicemail.VoicemailStatusHelper;
 import com.android.contacts.voicemail.VoicemailStatusHelper.StatusMessage;
 import com.android.contacts.voicemail.VoicemailStatusHelperImpl;
 import com.android.internal.telephony.CallerInfo;
 import com.android.internal.telephony.ITelephony;
-import com.google.common.annotations.VisibleForTesting;
 
 import android.app.KeyguardManager;
 import android.app.ListFragment;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.CommonDataKinds.SipAddress;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.PhoneLookup;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -60,108 +47,18 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.widget.ListView;
 import android.widget.TextView;
 
-import java.util.LinkedList;
 import java.util.List;
 
 /**
  * Displays a list of call log entries.
  */
 public class CallLogFragment extends ListFragment implements ViewPagerVisibilityListener,
-        CallLogQueryHandler.Listener {
+        CallLogQueryHandler.Listener, CallLogAdapter.CallFetcher {
     private static final String TAG = "CallLogFragment";
 
-    /** The size of the cache of contact info. */
-    private static final int CONTACT_INFO_CACHE_SIZE = 100;
-
-    /** The query for the call log table. */
-    public static final class CallLogQuery {
-        // If you alter this, you must also alter the method that inserts a fake row to the headers
-        // in the CallLogQueryHandler class called createHeaderCursorFor().
-        public static final String[] _PROJECTION = new String[] {
-                Calls._ID,
-                Calls.NUMBER,
-                Calls.DATE,
-                Calls.DURATION,
-                Calls.TYPE,
-                Calls.COUNTRY_ISO,
-                Calls.VOICEMAIL_URI,
-                Calls.GEOCODED_LOCATION,
-        };
-
-        public static final int ID = 0;
-        public static final int NUMBER = 1;
-        public static final int DATE = 2;
-        public static final int DURATION = 3;
-        public static final int CALL_TYPE = 4;
-        public static final int COUNTRY_ISO = 5;
-        public static final int VOICEMAIL_URI = 6;
-        public static final int GEOCODED_LOCATION = 7;
-
-        /**
-         * The name of the synthetic "section" column.
-         * <p>
-         * This column identifies whether a row is a header or an actual item, and whether it is
-         * part of the new or old calls.
-         */
-        public static final String SECTION_NAME = "section";
-        /** The index of the "section" column in the projection. */
-        public static final int SECTION = 8;
-        /** The value of the "section" column for the header of the new section. */
-        public static final int SECTION_NEW_HEADER = 0;
-        /** The value of the "section" column for the items of the new section. */
-        public static final int SECTION_NEW_ITEM = 1;
-        /** The value of the "section" column for the header of the old section. */
-        public static final int SECTION_OLD_HEADER = 2;
-        /** The value of the "section" column for the items of the old section. */
-        public static final int SECTION_OLD_ITEM = 3;
-
-        /** The call log projection including the section name. */
-        public static final String[] EXTENDED_PROJECTION;
-        static {
-            EXTENDED_PROJECTION = new String[_PROJECTION.length + 1];
-            System.arraycopy(_PROJECTION, 0, EXTENDED_PROJECTION, 0, _PROJECTION.length);
-            EXTENDED_PROJECTION[_PROJECTION.length] = SECTION_NAME;
-        }
-
-        public static boolean isSectionHeader(Cursor cursor) {
-            int section = cursor.getInt(CallLogQuery.SECTION);
-            return section == CallLogQuery.SECTION_NEW_HEADER
-                    || section == CallLogQuery.SECTION_OLD_HEADER;
-        }
-
-        public static boolean isNewSection(Cursor cursor) {
-            int section = cursor.getInt(CallLogQuery.SECTION);
-            return section == CallLogQuery.SECTION_NEW_ITEM
-                    || section == CallLogQuery.SECTION_NEW_HEADER;
-        }
-    }
-
-    /** The query to use for the phones table */
-    private static final class PhoneQuery {
-        public static final String[] _PROJECTION = new String[] {
-                PhoneLookup._ID,
-                PhoneLookup.DISPLAY_NAME,
-                PhoneLookup.TYPE,
-                PhoneLookup.LABEL,
-                PhoneLookup.NUMBER,
-                PhoneLookup.NORMALIZED_NUMBER,
-                PhoneLookup.PHOTO_THUMBNAIL_URI,
-                PhoneLookup.LOOKUP_KEY};
-
-        public static final int PERSON_ID = 0;
-        public static final int NAME = 1;
-        public static final int PHONE_TYPE = 2;
-        public static final int LABEL = 3;
-        public static final int MATCHED_NUMBER = 4;
-        public static final int NORMALIZED_NUMBER = 5;
-        public static final int THUMBNAIL_URI = 6;
-        public static final int LOOKUP_KEY = 7;
-    }
-
     private CallLogAdapter mAdapter;
     private CallLogQueryHandler mCallLogQueryHandler;
     private String mVoiceMailNumber;
@@ -177,702 +74,6 @@
     private TextView mStatusMessageAction;
     private KeyguardManager mKeyguardManager;
 
-    public static final class ContactInfo {
-        public long personId = -1;
-        public String name;
-        public int type;
-        public String label;
-        public String number;
-        public String formattedNumber;
-        public String normalizedNumber;
-        public Uri thumbnailUri;
-        public String lookupKey;
-
-        public static ContactInfo EMPTY = new ContactInfo();
-
-        @Override
-        public int hashCode() {
-            // Uses only name and personId to determine hashcode.
-            // This should be sufficient to have a reasonable distribution of hash codes.
-            // Moreover, there should be no two people with the same personId.
-            final int prime = 31;
-            int result = 1;
-            result = prime * result + (int) (personId ^ (personId >>> 32));
-            result = prime * result + ((name == null) ? 0 : name.hashCode());
-            return result;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) return true;
-            if (obj == null) return false;
-            if (getClass() != obj.getClass()) return false;
-            ContactInfo other = (ContactInfo) obj;
-            if (personId != other.personId) return false;
-            if (!TextUtils.equals(name, other.name)) return false;
-            if (type != other.type) return false;
-            if (!TextUtils.equals(label, other.label)) return false;
-            if (!TextUtils.equals(number, other.number)) return false;
-            // Ignore formatted number.
-            if (!TextUtils.equals(normalizedNumber, other.normalizedNumber)) return false;
-            if (!uriEquals(thumbnailUri, other.thumbnailUri)) return false;
-            if (!TextUtils.equals(lookupKey, other.lookupKey)) return false;
-            return true;
-        }
-
-        private static boolean uriEquals(Uri thumbnailUri1, Uri thumbnailUri2) {
-            if (thumbnailUri1 == thumbnailUri2) return true;
-            if (thumbnailUri1 == null) return false;
-            return thumbnailUri1.equals(thumbnailUri2);
-        }
-    }
-
-    public interface GroupCreator {
-        public void addGroup(int cursorPosition, int size, boolean expanded);
-    }
-
-    public interface CallFetcher {
-        public void fetchAllCalls();
-    }
-
-    /** Adapter class to fill in data for the Call Log */
-    public static final class CallLogAdapter extends GroupingListAdapter
-            implements Runnable, ViewTreeObserver.OnPreDrawListener, GroupCreator {
-        /** The time in millis to delay starting the thread processing requests. */
-        private static final int START_PROCESSING_REQUESTS_DELAY_MILLIS = 1000;
-
-        private final Context mContext;
-        private final String mCurrentCountryIso;
-        private final CallFetcher mCallFetcher;
-
-        /**
-         * A cache of the contact details for the phone numbers in the call log.
-         * <p>
-         * The content of the cache is expired (but not purged) whenever the application comes to
-         * the foreground.
-         */
-        private ExpirableCache<String, ContactInfo> mContactInfoCache;
-
-        /**
-         * List of requests to update contact details.
-         * <p>
-         * The requests are added when displaying the contacts and are processed by a background
-         * thread.
-         */
-        private final LinkedList<String> mRequests;
-
-        private volatile boolean mDone;
-        private boolean mLoading = true;
-        private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
-        private static final int REDRAW = 1;
-        private static final int START_THREAD = 2;
-        private boolean mFirst;
-        private Thread mCallerIdThread;
-
-        /** Instance of helper class for managing views. */
-        private final CallLogListItemHelper mCallLogViewsHelper;
-
-        /** Helper to set up contact photos. */
-        private final ContactPhotoManager mContactPhotoManager;
-        /** Helper to parse and process phone numbers. */
-        private PhoneNumberHelper mPhoneNumberHelper;
-        /** Helper to group call log entries. */
-        private final CallLogGroupBuilder mCallLogGroupBuilder;
-
-        /** Can be set to true by tests to disable processing of requests. */
-        private volatile boolean mRequestProcessingDisabled = false;
-
-        /** Listener for the primary action in the list, opens the call details. */
-        private final View.OnClickListener mPrimaryActionListener = new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                IntentProvider intentProvider = (IntentProvider) view.getTag();
-                if (intentProvider != null) {
-                    mContext.startActivity(intentProvider.getIntent(mContext));
-                }
-            }
-        };
-        /** Listener for the secondary action in the list, either call or play. */
-        private final View.OnClickListener mSecondaryActionListener = new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                IntentProvider intentProvider = (IntentProvider) view.getTag();
-                if (intentProvider != null) {
-                    mContext.startActivity(intentProvider.getIntent(mContext));
-                }
-            }
-        };
-
-        @Override
-        public boolean onPreDraw() {
-            if (mFirst) {
-                mHandler.sendEmptyMessageDelayed(START_THREAD,
-                        START_PROCESSING_REQUESTS_DELAY_MILLIS);
-                mFirst = false;
-            }
-            return true;
-        }
-
-        private Handler mHandler = new Handler() {
-            @Override
-            public void handleMessage(Message msg) {
-                switch (msg.what) {
-                    case REDRAW:
-                        notifyDataSetChanged();
-                        break;
-                    case START_THREAD:
-                        startRequestProcessing();
-                        break;
-                }
-            }
-        };
-
-        public CallLogAdapter(Context context, CallFetcher callFetcher,
-                String currentCountryIso, String voicemailNumber) {
-            super(context);
-
-            mContext = context;
-            mCurrentCountryIso = currentCountryIso;
-            mCallFetcher = callFetcher;
-
-            mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
-            mRequests = new LinkedList<String>();
-            mPreDrawListener = null;
-
-            Resources resources = mContext.getResources();
-            CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
-
-            mContactPhotoManager = ContactPhotoManager.getInstance(mContext);
-            mPhoneNumberHelper = new PhoneNumberHelper(resources, voicemailNumber);
-            PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(
-                    resources, callTypeHelper, mPhoneNumberHelper);
-            mCallLogViewsHelper =
-                    new CallLogListItemHelper(
-                            phoneCallDetailsHelper, mPhoneNumberHelper, resources);
-            mCallLogGroupBuilder = new CallLogGroupBuilder(this);
-        }
-
-        /**
-         * Requery on background thread when {@link Cursor} changes.
-         */
-        @Override
-        protected void onContentChanged() {
-            // When the content changes, always fetch all the calls, in case a new missed call came
-            // in and we were filtering over voicemail only, so that we see the missed call.
-            mCallFetcher.fetchAllCalls();
-        }
-
-        void setLoading(boolean loading) {
-            mLoading = loading;
-        }
-
-        @Override
-        public boolean isEmpty() {
-            if (mLoading) {
-                // We don't want the empty state to show when loading.
-                return false;
-            } else {
-                return super.isEmpty();
-            }
-        }
-
-        public ContactInfo getContactInfo(String number) {
-            return mContactInfoCache.getPossiblyExpired(number);
-        }
-
-        public void startRequestProcessing() {
-            if (mRequestProcessingDisabled) {
-                return;
-            }
-
-            mDone = false;
-            mCallerIdThread = new Thread(this, "CallLogContactLookup");
-            mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
-            mCallerIdThread.start();
-        }
-
-        /**
-         * Stops the background thread that processes updates and cancels any pending requests to
-         * start it.
-         * <p>
-         * Should be called from the main thread to prevent a race condition between the request to
-         * start the thread being processed and stopping the thread.
-         */
-        public void stopRequestProcessing() {
-            // Remove any pending requests to start the processing thread.
-            mHandler.removeMessages(START_THREAD);
-            mDone = true;
-            if (mCallerIdThread != null) mCallerIdThread.interrupt();
-        }
-
-        public void invalidateCache() {
-            mContactInfoCache.expireAll();
-        }
-
-        private void enqueueRequest(String number, boolean immediate) {
-            synchronized (mRequests) {
-                if (!mRequests.contains(number)) {
-                    mRequests.add(number);
-                    mRequests.notifyAll();
-                }
-            }
-            if (mFirst && immediate) {
-                startRequestProcessing();
-                mFirst = false;
-            }
-        }
-
-        /**
-         * Determines the contact information for the given SIP address.
-         * <p>
-         * It returns the contact info if found.
-         * <p>
-         * If no contact corresponds to the given SIP address, returns {@link ContactInfo#EMPTY}.
-         * <p>
-         * If the lookup fails for some other reason, it returns null.
-         */
-        private ContactInfo queryContactInfoForSipAddress(String sipAddress) {
-            final ContactInfo info;
-
-            // TODO: This code is duplicated from the
-            // CallerInfoAsyncQuery class.  To avoid that, could the
-            // code here just use CallerInfoAsyncQuery, rather than
-            // manually running ContentResolver.query() itself?
-
-            // We look up SIP addresses directly in the Data table:
-            Uri contactRef = Data.CONTENT_URI;
-
-            // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
-            //
-            // Also note we use "upper(data1)" in the WHERE clause, and
-            // uppercase the incoming SIP address, in order to do a
-            // case-insensitive match.
-            //
-            // TODO: May also need to normalize by adding "sip:" as a
-            // prefix, if we start storing SIP addresses that way in the
-            // database.
-            String selection = "upper(" + Data.DATA1 + ")=?"
-                    + " AND "
-                    + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
-            String[] selectionArgs = new String[] { sipAddress.toUpperCase() };
-
-            Cursor dataTableCursor =
-                    mContext.getContentResolver().query(
-                            contactRef,
-                            null,  // projection
-                            selection,  // selection
-                            selectionArgs,  // selectionArgs
-                            null);  // sortOrder
-
-            if (dataTableCursor != null) {
-                if (dataTableCursor.moveToFirst()) {
-                    info = new ContactInfo();
-
-                    // TODO: we could slightly speed this up using an
-                    // explicit projection (and thus not have to do
-                    // those getColumnIndex() calls) but the benefit is
-                    // very minimal.
-
-                    // Note the Data.CONTACT_ID column here is
-                    // equivalent to the PERSON_ID_COLUMN_INDEX column
-                    // we use with "phonesCursor" below.
-                    info.personId = dataTableCursor.getLong(
-                            dataTableCursor.getColumnIndex(Data.CONTACT_ID));
-                    info.name = dataTableCursor.getString(
-                            dataTableCursor.getColumnIndex(Data.DISPLAY_NAME));
-                    // "type" and "label" are currently unused for SIP addresses
-                    info.type = SipAddress.TYPE_OTHER;
-                    info.label = null;
-
-                    // And "number" is the SIP address.
-                    // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
-                    info.number = dataTableCursor.getString(
-                            dataTableCursor.getColumnIndex(Data.DATA1));
-                    info.normalizedNumber = null;  // meaningless for SIP addresses
-                    final String thumbnailUriString = dataTableCursor.getString(
-                            dataTableCursor.getColumnIndex(Data.PHOTO_THUMBNAIL_URI));
-                    info.thumbnailUri = thumbnailUriString == null
-                            ? null
-                            : Uri.parse(thumbnailUriString);
-                    info.lookupKey = dataTableCursor.getString(
-                            dataTableCursor.getColumnIndex(Data.LOOKUP_KEY));
-                } else {
-                    info = ContactInfo.EMPTY;
-                }
-                dataTableCursor.close();
-            } else {
-                // Failed to fetch the data, ignore this request.
-                info = null;
-            }
-            return info;
-        }
-
-        /**
-         * Determines the contact information for the given phone number.
-         * <p>
-         * It returns the contact info if found.
-         * <p>
-         * If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
-         * <p>
-         * If the lookup fails for some other reason, it returns null.
-         */
-        private ContactInfo queryContactInfoForPhoneNumber(String number) {
-            final ContactInfo info;
-
-            // "number" is a regular phone number, so use the
-            // PhoneLookup table:
-            Cursor phonesCursor =
-                    mContext.getContentResolver().query(
-                        Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
-                                Uri.encode(number)),
-                                PhoneQuery._PROJECTION, null, null, null);
-            if (phonesCursor != null) {
-                if (phonesCursor.moveToFirst()) {
-                    info = new ContactInfo();
-                    info.personId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
-                    info.name = phonesCursor.getString(PhoneQuery.NAME);
-                    info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
-                    info.label = phonesCursor.getString(PhoneQuery.LABEL);
-                    info.number = phonesCursor
-                            .getString(PhoneQuery.MATCHED_NUMBER);
-                    info.normalizedNumber = phonesCursor
-                            .getString(PhoneQuery.NORMALIZED_NUMBER);
-                    final String thumbnailUriString = phonesCursor.getString(
-                            PhoneQuery.THUMBNAIL_URI);
-                    info.thumbnailUri = thumbnailUriString == null
-                            ? null
-                            : Uri.parse(thumbnailUriString);
-                    info.lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
-                } else {
-                    info = ContactInfo.EMPTY;
-                }
-                phonesCursor.close();
-            } else {
-                // Failed to fetch the data, ignore this request.
-                info = null;
-            }
-            return info;
-        }
-
-        /**
-         * Queries the appropriate content provider for the contact associated with the number.
-         * <p>
-         * The number might be either a SIP address or a phone number.
-         * <p>
-         * It returns true if it updated the content of the cache and we should therefore tell the
-         * view to update its content.
-         */
-        private boolean queryContactInfo(String number) {
-            final ContactInfo info;
-
-            // Determine the contact info.
-            if (PhoneNumberUtils.isUriNumber(number)) {
-                // This "number" is really a SIP address.
-                info = queryContactInfoForSipAddress(number);
-            } else {
-                info = queryContactInfoForPhoneNumber(number);
-            }
-
-            if (info == null) {
-                // The lookup failed, just return without requesting to update the view.
-                return false;
-            }
-
-            // Check the existing entry in the cache: only if it has changed we should update the
-            // view.
-            ContactInfo existingInfo = mContactInfoCache.getPossiblyExpired(number);
-            boolean updated = !info.equals(existingInfo);
-            if (updated) {
-                // The formattedNumber is computed by the UI thread when needed. Since we updated
-                // the details of the contact, set this value to null for now.
-                info.formattedNumber = null;
-            }
-            // Store the data in the cache so that the UI thread can use to display it. Store it
-            // even if it has not changed so that it is marked as not expired.
-            mContactInfoCache.put(number, info);
-            return updated;
-        }
-
-        /*
-         * Handles requests for contact name and number type
-         * @see java.lang.Runnable#run()
-         */
-        @Override
-        public void run() {
-            boolean needNotify = false;
-            while (!mDone) {
-                String number = null;
-                synchronized (mRequests) {
-                    if (!mRequests.isEmpty()) {
-                        number = mRequests.removeFirst();
-                    } else {
-                        if (needNotify) {
-                            needNotify = false;
-                            mHandler.sendEmptyMessage(REDRAW);
-                        }
-                        try {
-                            mRequests.wait(1000);
-                        } catch (InterruptedException ie) {
-                            // Ignore and continue processing requests
-                            Thread.currentThread().interrupt();
-                        }
-                    }
-                }
-                if (!mDone && number != null && queryContactInfo(number)) {
-                    needNotify = true;
-                }
-            }
-        }
-
-        @Override
-        protected void addGroups(Cursor cursor) {
-            mCallLogGroupBuilder.addGroups(cursor);
-        }
-
-        @VisibleForTesting
-        @Override
-        public View newStandAloneView(Context context, ViewGroup parent) {
-            LayoutInflater inflater =
-                    (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
-            findAndCacheViews(view);
-            return view;
-        }
-
-        @VisibleForTesting
-        @Override
-        public void bindStandAloneView(View view, Context context, Cursor cursor) {
-            bindView(view, cursor, 1);
-        }
-
-        @VisibleForTesting
-        @Override
-        public View newChildView(Context context, ViewGroup parent) {
-            LayoutInflater inflater =
-                    (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
-            findAndCacheViews(view);
-            return view;
-        }
-
-        @VisibleForTesting
-        @Override
-        public void bindChildView(View view, Context context, Cursor cursor) {
-            bindView(view, cursor, 1);
-        }
-
-        @VisibleForTesting
-        @Override
-        public View newGroupView(Context context, ViewGroup parent) {
-            LayoutInflater inflater =
-                    (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
-            findAndCacheViews(view);
-            return view;
-        }
-
-        @VisibleForTesting
-        @Override
-        public void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
-                boolean expanded) {
-            bindView(view, cursor, groupSize);
-        }
-
-        private void findAndCacheViews(View view) {
-            // Get the views to bind to.
-            CallLogListItemViews views = CallLogListItemViews.fromView(view);
-            views.primaryActionView.setOnClickListener(mPrimaryActionListener);
-            views.secondaryActionView.setOnClickListener(mSecondaryActionListener);
-            view.setTag(views);
-        }
-
-        /**
-         * Binds the views in the entry to the data in the call log.
-         *
-         * @param view the view corresponding to this entry
-         * @param c the cursor pointing to the entry in the call log
-         * @param count the number of entries in the current item, greater than 1 if it is a group
-         */
-        private void bindView(View view, Cursor c, int count) {
-            final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
-            final int section = c.getInt(CallLogQuery.SECTION);
-
-            // This might be a header: check the value of the section column in the cursor.
-            if (section == CallLogQuery.SECTION_NEW_HEADER
-                    || section == CallLogQuery.SECTION_OLD_HEADER) {
-                views.listItemView.setVisibility(View.GONE);
-                views.listHeaderView.setVisibility(View.VISIBLE);
-                views.listHeaderTextView.setText(
-                        section == CallLogQuery.SECTION_NEW_HEADER
-                                ? R.string.call_log_new_header
-                                : R.string.call_log_old_header);
-                // Nothing else to set up for a header.
-                return;
-            }
-            // Default case: an item in the call log.
-            views.listItemView.setVisibility(View.VISIBLE);
-            views.listHeaderView.setVisibility(View.GONE);
-
-            final String number = c.getString(CallLogQuery.NUMBER);
-            final long date = c.getLong(CallLogQuery.DATE);
-            final long duration = c.getLong(CallLogQuery.DURATION);
-            final int callType = c.getInt(CallLogQuery.CALL_TYPE);
-            final String formattedNumber;
-            final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
-
-            views.primaryActionView.setTag(
-                    IntentProvider.getCallDetailIntentProvider(
-                            this, c.getPosition(), c.getLong(CallLogQuery.ID), count));
-            // Store away the voicemail information so we can play it directly.
-            if (callType == Calls.VOICEMAIL_TYPE) {
-                String voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
-                final long rowId = c.getLong(CallLogQuery.ID);
-                views.secondaryActionView.setTag(
-                        IntentProvider.getPlayVoicemailIntentProvider(rowId, voicemailUri));
-            } else if (!TextUtils.isEmpty(number)) {
-                // Store away the number so we can call it directly if you click on the call icon.
-                views.secondaryActionView.setTag(
-                        IntentProvider.getReturnCallIntentProvider(number));
-            } else {
-                // No action enabled.
-                views.secondaryActionView.setTag(null);
-            }
-
-            // Lookup contacts with this number
-            ExpirableCache.CachedValue<ContactInfo> cachedInfo =
-                    mContactInfoCache.getCachedValue(number);
-            ContactInfo info = cachedInfo == null ? null : cachedInfo.getValue();
-            if (cachedInfo == null) {
-                // Mark it as empty and queue up a request to find the name.
-                // The db request should happen on a non-UI thread.
-                info = ContactInfo.EMPTY;
-                mContactInfoCache.put(number, info);
-                // Request the contact details immediately since they are currently missing.
-                enqueueRequest(number, true);
-                // Format the phone number in the call log as best as we can.
-                formattedNumber = formatPhoneNumber(number, null, countryIso);
-            } else {
-                if (cachedInfo.isExpired()) {
-                    // The contact info is no longer up to date, we should request it. However, we
-                    // do not need to request them immediately.
-                    enqueueRequest(number, false);
-                }
-
-                if (info != ContactInfo.EMPTY) {
-                    // Format and cache phone number for found contact.
-                    if (info.formattedNumber == null) {
-                        info.formattedNumber =
-                                formatPhoneNumber(info.number, info.normalizedNumber, countryIso);
-                    }
-                    formattedNumber = info.formattedNumber;
-                } else {
-                    // Format the phone number in the call log as best as we can.
-                    formattedNumber = formatPhoneNumber(number, null, countryIso);
-                }
-            }
-
-            final long personId = info.personId;
-            final String name = info.name;
-            final int ntype = info.type;
-            final String label = info.label;
-            final Uri thumbnailUri = info.thumbnailUri;
-            final String lookupKey = info.lookupKey;
-            final int[] callTypes = getCallTypes(c, count);
-            final String geocode = c.getString(CallLogQuery.GEOCODED_LOCATION);
-            final PhoneCallDetails details;
-            if (TextUtils.isEmpty(name)) {
-                details = new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
-                        callTypes, date, duration);
-            } else {
-                details = new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
-                        callTypes, date, duration, name, ntype, label, personId, thumbnailUri);
-            }
-
-            final boolean isNew = CallLogQuery.isNewSection(c);
-            // New items also use the highlighted version of the text.
-            final boolean isHighlighted = isNew;
-            mCallLogViewsHelper.setPhoneCallDetails(views, details, isHighlighted);
-            setPhoto(views, thumbnailUri, personId, lookupKey);
-
-            // Listen for the first draw
-            if (mPreDrawListener == null) {
-                mFirst = true;
-                mPreDrawListener = this;
-                view.getViewTreeObserver().addOnPreDrawListener(this);
-            }
-        }
-
-        /**
-         * Returns the call types for the given number of items in the cursor.
-         * <p>
-         * It uses the next {@code count} rows in the cursor to extract the types.
-         * <p>
-         * It position in the cursor is unchanged by this function.
-         */
-        private int[] getCallTypes(Cursor cursor, int count) {
-            int position = cursor.getPosition();
-            int[] callTypes = new int[count];
-            for (int index = 0; index < count; ++index) {
-                callTypes[index] = cursor.getInt(CallLogQuery.CALL_TYPE);
-                cursor.moveToNext();
-            }
-            cursor.moveToPosition(position);
-            return callTypes;
-        }
-
-        private void setPhoto(CallLogListItemViews views, Uri thumbnailUri, long contactId,
-                String lookupKey) {
-            views.quickContactView.assignContactUri(contactId == -1 ? null :
-                    Contacts.getLookupUri(contactId, lookupKey));
-            mContactPhotoManager.loadPhoto(views.quickContactView, thumbnailUri);
-        }
-
-        /**
-         * Sets whether processing of requests for contact details should be enabled.
-         * <p>
-         * This method should be called in tests to disable such processing of requests when not
-         * needed.
-         */
-        public void disableRequestProcessingForTest() {
-            mRequestProcessingDisabled = true;
-        }
-
-        public void injectContactInfoForTest(String number, ContactInfo contactInfo) {
-            mContactInfoCache.put(number, contactInfo);
-        }
-
-        @Override
-        public void addGroup(int cursorPosition, int size, boolean expanded) {
-            super.addGroup(cursorPosition, size, expanded);
-        }
-
-        /**
-         * Format the given phone number
-         *
-         * @param number the number to be formatted.
-         * @param normalizedNumber the normalized number of the given number.
-         * @param countryIso the ISO 3166-1 two letters country code, the country's
-         *        convention will be used to format the number if the normalized
-         *        phone is null.
-         *
-         * @return the formatted number, or the given number if it was formatted.
-         */
-        private String formatPhoneNumber(String number, String normalizedNumber,
-                String countryIso) {
-            if (TextUtils.isEmpty(number)) {
-                return "";
-            }
-            // If "number" is really a SIP address, don't try to do any formatting at all.
-            if (PhoneNumberUtils.isUriNumber(number)) {
-                return number;
-            }
-            if (TextUtils.isEmpty(countryIso)) {
-                countryIso = mCurrentCountryIso;
-            }
-            return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
-        }
-    }
-
     @Override
     public void onCreate(Bundle state) {
         super.onCreate(state);
@@ -928,13 +129,7 @@
     public void onViewCreated(View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         String currentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
-        mAdapter = new CallLogAdapter(getActivity(),
-                new CallFetcher() {
-                    @Override
-                    public void fetchAllCalls() {
-                        startCallsQuery();
-                    }
-                }, currentCountryIso, mVoiceMailNumber);
+        mAdapter = new CallLogAdapter(getActivity(), this, currentCountryIso, mVoiceMailNumber);
         setListAdapter(mAdapter);
         getListView().setItemsCanFocus(true);
     }
@@ -1000,7 +195,8 @@
         mAdapter.changeCursor(null);
     }
 
-    private void startCallsQuery() {
+    @Override
+    public void startCallsQuery() {
         mAdapter.setLoading(true);
         mCallLogQueryHandler.fetchAllCalls();
         if (mShowingVoicemailOnly) {
@@ -1024,8 +220,6 @@
     @Override
     public void onPrepareOptionsMenu(Menu menu) {
         if (mShowOptionsMenu) {
-            menu.findItem(R.id.menu_call_settings_call_log)
-                .setIntent(DialtactsActivity.getCallSettingsIntent());
             menu.findItem(R.id.show_voicemails_only).setVisible(!mShowingVoicemailOnly);
             menu.findItem(R.id.show_all_calls).setVisible(mShowingVoicemailOnly);
         }
@@ -1052,45 +246,6 @@
                 return false;
         }
     }
-
-    /*
-     * Get the number from the Contacts, if available, since sometimes
-     * the number provided by caller id may not be formatted properly
-     * depending on the carrier (roaming) in use at the time of the
-     * incoming call.
-     * Logic : If the caller-id number starts with a "+", use it
-     *         Else if the number in the contacts starts with a "+", use that one
-     *         Else if the number in the contacts is longer, use that one
-     */
-    private String getBetterNumberFromContacts(String number) {
-        String matchingNumber = null;
-        // Look in the cache first. If it's not found then query the Phones db
-        ContactInfo ci = mAdapter.mContactInfoCache.getPossiblyExpired(number);
-        if (ci != null && ci != ContactInfo.EMPTY) {
-            matchingNumber = ci.number;
-        } else {
-            try {
-                Cursor phonesCursor = getActivity().getContentResolver().query(
-                        Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, number),
-                        PhoneQuery._PROJECTION, null, null, null);
-                if (phonesCursor != null) {
-                    if (phonesCursor.moveToFirst()) {
-                        matchingNumber = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
-                    }
-                    phonesCursor.close();
-                }
-            } catch (Exception e) {
-                // Use the number from the call log
-            }
-        }
-        if (!TextUtils.isEmpty(matchingNumber) &&
-                (matchingNumber.startsWith("+")
-                        || matchingNumber.length() > number.length())) {
-            number = matchingNumber;
-        }
-        return number;
-    }
-
     public void callSelectedEntry() {
         int position = getListView().getSelectedItemPosition();
         if (position < 0) {
@@ -1122,7 +277,7 @@
                        (callType == Calls.INCOMING_TYPE
                                 || callType == Calls.MISSED_TYPE)) {
                     // If the caller-id matches a contact with a better qualified number, use it
-                    number = getBetterNumberFromContacts(number);
+                    number = mAdapter.getBetterNumberFromContacts(number);
                 }
                 intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
                                     Uri.fromParts("tel", number, null));
@@ -1162,7 +317,6 @@
         mAdapter.invalidateCache();
         startCallsQuery();
         startVoicemailStatusQuery();
-        mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
         updateOnEntry();
     }
 
diff --git a/src/com/android/contacts/calllog/CallLogGroupBuilder.java b/src/com/android/contacts/calllog/CallLogGroupBuilder.java
index f5aef3f..a7f8fa3 100644
--- a/src/com/android/contacts/calllog/CallLogGroupBuilder.java
+++ b/src/com/android/contacts/calllog/CallLogGroupBuilder.java
@@ -17,7 +17,6 @@
 package com.android.contacts.calllog;
 
 import com.android.common.widget.GroupingListAdapter;
-import com.android.contacts.calllog.CallLogFragment.CallLogQuery;
 
 import android.database.CharArrayBuffer;
 import android.database.Cursor;
@@ -30,15 +29,19 @@
  * This class is meant to be used in conjunction with {@link GroupingListAdapter}.
  */
 public class CallLogGroupBuilder {
+    public interface GroupCreator {
+        public void addGroup(int cursorPosition, int size, boolean expanded);
+    }
+
     /** Reusable char array buffer. */
     private CharArrayBuffer mBuffer1 = new CharArrayBuffer(128);
     /** Reusable char array buffer. */
     private CharArrayBuffer mBuffer2 = new CharArrayBuffer(128);
 
     /** The object on which the groups are created. */
-    private final CallLogFragment.GroupCreator mGroupCreator;
+    private final GroupCreator mGroupCreator;
 
-    public CallLogGroupBuilder(CallLogFragment.GroupCreator groupCreator) {
+    public CallLogGroupBuilder(GroupCreator groupCreator) {
         mGroupCreator = groupCreator;
     }
 
@@ -74,7 +77,7 @@
             final boolean sameNumber = equalPhoneNumbers(firstNumber, currentNumber);
             final boolean shouldGroup;
 
-            if (CallLogFragment.CallLogQuery.isSectionHeader(cursor)) {
+            if (CallLogQuery.isSectionHeader(cursor)) {
                 // Cannot group headers.
                 shouldGroup = false;
             } else if (!sameNumber) {
@@ -120,7 +123,7 @@
      * <p>
      * The group is always unexpanded.
      *
-     * @see CallLogFragment.CallLogAdapter#addGroup(int, int, boolean)
+     * @see CallLogAdapter#addGroup(int, int, boolean)
      */
     private void addGroup(int cursorPosition, int size) {
         mGroupCreator.addGroup(cursorPosition, size, false);
diff --git a/src/com/android/contacts/calllog/CallLogQuery.java b/src/com/android/contacts/calllog/CallLogQuery.java
new file mode 100644
index 0000000..f596032
--- /dev/null
+++ b/src/com/android/contacts/calllog/CallLogQuery.java
@@ -0,0 +1,91 @@
+/*
+ * 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.calllog;
+
+import android.database.Cursor;
+import android.provider.CallLog.Calls;
+
+/**
+ * The query for the call log table.
+ */
+public final class CallLogQuery {
+    // If you alter this, you must also alter the method that inserts a fake row to the headers
+    // in the CallLogQueryHandler class called createHeaderCursorFor().
+    public static final String[] _PROJECTION = new String[] {
+            Calls._ID,
+            Calls.NUMBER,
+            Calls.DATE,
+            Calls.DURATION,
+            Calls.TYPE,
+            Calls.COUNTRY_ISO,
+            Calls.VOICEMAIL_URI,
+            Calls.GEOCODED_LOCATION,
+            Calls.CACHED_NAME,
+            Calls.CACHED_NUMBER_TYPE,
+            Calls.CACHED_NUMBER_LABEL,
+    };
+
+    public static final int ID = 0;
+    public static final int NUMBER = 1;
+    public static final int DATE = 2;
+    public static final int DURATION = 3;
+    public static final int CALL_TYPE = 4;
+    public static final int COUNTRY_ISO = 5;
+    public static final int VOICEMAIL_URI = 6;
+    public static final int GEOCODED_LOCATION = 7;
+    public static final int CACHED_NAME = 8;
+    public static final int CACHED_NUMBER_TYPE = 9;
+    public static final int CACHED_NUMBER_LABEL = 10;
+
+    /**
+     * The name of the synthetic "section" column.
+     * <p>
+     * This column identifies whether a row is a header or an actual item, and whether it is
+     * part of the new or old calls.
+     */
+    public static final String SECTION_NAME = "section";
+    /** The index of the "section" column in the projection. */
+    public static final int SECTION = 11;
+    /** The value of the "section" column for the header of the new section. */
+    public static final int SECTION_NEW_HEADER = 0;
+    /** The value of the "section" column for the items of the new section. */
+    public static final int SECTION_NEW_ITEM = 1;
+    /** The value of the "section" column for the header of the old section. */
+    public static final int SECTION_OLD_HEADER = 2;
+    /** The value of the "section" column for the items of the old section. */
+    public static final int SECTION_OLD_ITEM = 3;
+
+    /** The call log projection including the section name. */
+    public static final String[] EXTENDED_PROJECTION;
+    static {
+        EXTENDED_PROJECTION = new String[_PROJECTION.length + 1];
+        System.arraycopy(_PROJECTION, 0, EXTENDED_PROJECTION, 0, _PROJECTION.length);
+        EXTENDED_PROJECTION[_PROJECTION.length] = SECTION_NAME;
+    }
+
+    public static boolean isSectionHeader(Cursor cursor) {
+        int section = cursor.getInt(CallLogQuery.SECTION);
+        return section == CallLogQuery.SECTION_NEW_HEADER
+                || section == CallLogQuery.SECTION_OLD_HEADER;
+    }
+
+    public static boolean isNewSection(Cursor cursor) {
+        int section = cursor.getInt(CallLogQuery.SECTION);
+        return section == CallLogQuery.SECTION_NEW_ITEM
+                || section == CallLogQuery.SECTION_NEW_HEADER;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/calllog/CallLogQueryHandler.java b/src/com/android/contacts/calllog/CallLogQueryHandler.java
index 979db4b..25beba5 100644
--- a/src/com/android/contacts/calllog/CallLogQueryHandler.java
+++ b/src/com/android/contacts/calllog/CallLogQueryHandler.java
@@ -17,7 +17,6 @@
 package com.android.contacts.calllog;
 
 import com.android.common.io.MoreCloseables;
-import com.android.contacts.calllog.CallLogFragment.CallLogQuery;
 import com.android.contacts.voicemail.VoicemailStatusHelperImpl;
 
 import android.content.AsyncQueryHandler;
@@ -104,10 +103,10 @@
     /** Creates a cursor that contains a single row and maps the section to the given value. */
     private Cursor createHeaderCursorFor(int section) {
         MatrixCursor matrixCursor =
-                new MatrixCursor(CallLogFragment.CallLogQuery.EXTENDED_PROJECTION);
+                new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
         // The values in this row correspond to default values for _PROJECTION from CallLogQuery
         // plus the section value.
-        matrixCursor.addRow(new Object[]{ -1L, "", 0L, 0L, 0, "", "", "", section });
+        matrixCursor.addRow(new Object[]{ -1L, "", 0L, 0L, 0, "", "", "", null, 0, null, section });
         return matrixCursor;
     }
 
diff --git a/src/com/android/contacts/calllog/ContactInfo.java b/src/com/android/contacts/calllog/ContactInfo.java
new file mode 100644
index 0000000..1f106e4
--- /dev/null
+++ b/src/com/android/contacts/calllog/ContactInfo.java
@@ -0,0 +1,73 @@
+/*
+ * 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.calllog;
+
+import android.net.Uri;
+import android.text.TextUtils;
+
+/**
+ * Information for a contact as needed by the Call Log.
+ */
+public final class ContactInfo {
+    public long personId = -1;
+    public String name;
+    public int type;
+    public String label;
+    public String number;
+    public String formattedNumber;
+    public String normalizedNumber;
+    public Uri thumbnailUri;
+    public String lookupKey;
+
+    public static ContactInfo EMPTY = new ContactInfo();
+
+    @Override
+    public int hashCode() {
+        // Uses only name and personId to determine hashcode.
+        // This should be sufficient to have a reasonable distribution of hash codes.
+        // Moreover, there should be no two people with the same personId.
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (int) (personId ^ (personId >>> 32));
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        ContactInfo other = (ContactInfo) obj;
+        if (personId != other.personId) return false;
+        if (!TextUtils.equals(name, other.name)) return false;
+        if (type != other.type) return false;
+        if (!TextUtils.equals(label, other.label)) return false;
+        if (!TextUtils.equals(number, other.number)) return false;
+        // Ignore formatted number.
+        if (!TextUtils.equals(normalizedNumber, other.normalizedNumber)) return false;
+        if (!uriEquals(thumbnailUri, other.thumbnailUri)) return false;
+        if (!TextUtils.equals(lookupKey, other.lookupKey)) return false;
+        return true;
+    }
+
+    private static boolean uriEquals(Uri thumbnailUri1, Uri thumbnailUri2) {
+        if (thumbnailUri1 == thumbnailUri2) return true;
+        if (thumbnailUri1 == null) return false;
+        return thumbnailUri1.equals(thumbnailUri2);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/calllog/IntentProvider.java b/src/com/android/contacts/calllog/IntentProvider.java
index 9ce3d3d..bfee5ec 100644
--- a/src/com/android/contacts/calllog/IntentProvider.java
+++ b/src/com/android/contacts/calllog/IntentProvider.java
@@ -17,8 +17,6 @@
 package com.android.contacts.calllog;
 
 import com.android.contacts.CallDetailActivity;
-import com.android.contacts.calllog.CallLogFragment.CallLogAdapter;
-import com.android.contacts.calllog.CallLogFragment.CallLogQuery;
 
 import android.content.ContentUris;
 import android.content.Context;
diff --git a/src/com/android/contacts/calllog/PhoneQuery.java b/src/com/android/contacts/calllog/PhoneQuery.java
new file mode 100644
index 0000000..52faa8b
--- /dev/null
+++ b/src/com/android/contacts/calllog/PhoneQuery.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.calllog;
+
+import android.provider.ContactsContract.PhoneLookup;
+
+/**
+ * The query to look up the {@link ContactInfo} for a given number in the Call Log.
+ */
+final class PhoneQuery {
+    public static final String[] _PROJECTION = new String[] {
+            PhoneLookup._ID,
+            PhoneLookup.DISPLAY_NAME,
+            PhoneLookup.TYPE,
+            PhoneLookup.LABEL,
+            PhoneLookup.NUMBER,
+            PhoneLookup.NORMALIZED_NUMBER,
+            PhoneLookup.PHOTO_THUMBNAIL_URI,
+            PhoneLookup.LOOKUP_KEY};
+
+    public static final int PERSON_ID = 0;
+    public static final int NAME = 1;
+    public static final int PHONE_TYPE = 2;
+    public static final int LABEL = 3;
+    public static final int MATCHED_NUMBER = 4;
+    public static final int NORMALIZED_NUMBER = 5;
+    public static final int THUMBNAIL_URI = 6;
+    public static final int LOOKUP_KEY = 7;
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
index b2a203b..ba86f9f 100644
--- a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
@@ -19,12 +19,16 @@
 import com.android.contacts.ContactLoader;
 import com.android.contacts.R;
 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.util.StreamItemEntry;
 
 import android.app.ListFragment;
+import android.content.ContentUris;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.provider.ContactsContract.StreamItems;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -69,27 +73,16 @@
                 // Ignore if this item does not have a stream item associated with it.
                 return;
             }
-            String actionUri = streamItemEntry.getActionUri();
-            if (actionUri == null) {
-                // Ignore if this item does not have a URI.
-                return;
-            }
-            // Parse the URI.
-            Uri uri;
-            try {
-                uri = Uri.parse(actionUri);
-            } catch (Throwable throwable) {
-                // This may fail if the URI is invalid: instead of failing, just ignore it.
-                Log.e(TAG, "invalid URI for stream item #" + streamItemEntry.getId() + ": "
-                        + actionUri);
-                return;
-            }
-            String action = streamItemEntry.getAction();
-            if (action == null) {
-                // Ignore if this item does not have an action.
-                return;
-            }
-            startActivity(new Intent(action, uri));
+            final AccountTypeManager manager = AccountTypeManager.getInstance(getActivity());
+            final AccountType accountType = manager.getAccountType(
+                    streamItemEntry.getAccountType(), streamItemEntry.getDataSet());
+
+            final Uri uri = ContentUris.withAppendedId(StreamItems.CONTENT_URI,
+                    streamItemEntry.getId());
+            final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+            intent.setClassName(accountType.resPackageName,
+                    accountType.getViewStreamItemActivity());
+            startActivity(intent);
         }
     };
 
diff --git a/src/com/android/contacts/detail/StreamItemAdapter.java b/src/com/android/contacts/detail/StreamItemAdapter.java
index 09aa20b..5128787 100644
--- a/src/com/android/contacts/detail/StreamItemAdapter.java
+++ b/src/com/android/contacts/detail/StreamItemAdapter.java
@@ -17,6 +17,8 @@
 package com.android.contacts.detail;
 
 import com.android.contacts.R;
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.util.StreamItemEntry;
 import com.google.android.collect.Lists;
 
@@ -84,7 +86,10 @@
         StreamItemEntry streamItem = (StreamItemEntry) getItem(position);
         View view = ContactDetailDisplayUtils.createStreamItemView(
                 mInflater, mContext, streamItem, null);
-        if (streamItem.getAction() != null && streamItem.getActionUri() != null) {
+        final AccountTypeManager manager = AccountTypeManager.getInstance(mContext);
+        final AccountType accountType =
+                manager.getAccountType(streamItem.getAccountType(), streamItem.getDataSet());
+        if (accountType.getViewStreamItemActivity() != null) {
             view.setTag(streamItem);
             view.setFocusable(true);
             view.setOnClickListener(mListener);
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index 720c33a..62ba95e 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -571,14 +571,10 @@
     }
 
     private void setupMenuItems(Menu menu) {
-        final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings_dialpad);
         final MenuItem addToContactMenuItem = menu.findItem(R.id.menu_add_contacts);
         final MenuItem twoSecPauseMenuItem = menu.findItem(R.id.menu_2s_pause);
         final MenuItem waitMenuItem = menu.findItem(R.id.menu_add_wait);
 
-        callSettingsMenuItem.setVisible(true);
-        callSettingsMenuItem.setIntent(DialtactsActivity.getCallSettingsIntent());
-
         // We show "add to contacts", "2sec pause", and "add wait" menus only when the user is
         // seeing usual dialpads and has typed at least one digit.
         // We never show a menu if the "choose dialpad" UI is up.
diff --git a/src/com/android/contacts/group/GroupDetailFragment.java b/src/com/android/contacts/group/GroupDetailFragment.java
index 1205c31..1f47510 100644
--- a/src/com/android/contacts/group/GroupDetailFragment.java
+++ b/src/com/android/contacts/group/GroupDetailFragment.java
@@ -30,6 +30,7 @@
 import android.app.Fragment;
 import android.app.LoaderManager;
 import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentUris;
 import android.content.Context;
 import android.content.CursorLoader;
 import android.content.Intent;
@@ -38,6 +39,7 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
+import android.provider.ContactsContract.Groups;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -68,10 +70,9 @@
         public void onGroupSizeUpdated(String size);
 
         /**
-         * The group source (intent action and action URI) has been determined.
+         * The account type and dataset have been determined.
          */
-        public void onGroupSourceUpdated(String accountTypeString, String dataSet,
-                String groupSourceAction, String groupSourceUri);
+        public void onAccountTypeUpdated(String accountTypeString, String dataSet);
 
         /**
          * User decided to go to Edit-Mode
@@ -277,9 +278,7 @@
 
             final String accountTypeString = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
             final String dataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
-            final String groupSourceAction = cursor.getString(GroupMetaDataLoader.ACTION);
-            final String groupSourceUri = cursor.getString(GroupMetaDataLoader.ACTION_URI);
-            updateGroupSource(accountTypeString, dataSet, groupSourceAction, groupSourceUri);
+            updateAccountType(accountTypeString, dataSet);
         }
     }
 
@@ -322,21 +321,23 @@
      * a button in a static header on the page, or as a header that scrolls with the
      * {@link ListView}.
      */
-    private void updateGroupSource(final String accountTypeString, final String dataSet,
-            final String groupSourceAction, final String groupSourceUri) {
+    private void updateAccountType(final String accountTypeString, final String dataSet) {
 
         // If the group action should be shown in the action bar, then pass the data to the
         // listener who will take care of setting up the view and click listener. There is nothing
         // else to be done by this {@link Fragment}.
         if (mShowGroupActionInActionBar) {
-            mListener.onGroupSourceUpdated(accountTypeString, dataSet, groupSourceAction,
-                    groupSourceUri);
+            mListener.onAccountTypeUpdated(accountTypeString, dataSet);
             return;
         }
 
+        final AccountTypeManager manager = AccountTypeManager.getInstance(getActivity());
+        final AccountType accountType =
+                manager.getAccountType(accountTypeString, dataSet);
+
         // Otherwise, if the {@link Fragment} needs to create and setup the button, then first
         // verify that there is a valid action.
-        if (!TextUtils.isEmpty(groupSourceAction) && !TextUtils.isEmpty(groupSourceUri)) {
+        if (!TextUtils.isEmpty(accountType.getViewGroupActivity())) {
             if (mGroupSourceView == null) {
                 mGroupSourceView = GroupDetailDisplayUtils.getNewGroupSourceView(mContext);
                 // Figure out how to add the view to the fragment.
@@ -358,7 +359,11 @@
             mGroupSourceView.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
-                    startActivity(new Intent(groupSourceAction, Uri.parse(groupSourceUri)));
+                    final Uri uri = ContentUris.withAppendedId(Groups.CONTENT_URI, mGroupId);
+                    final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+                    intent.setClassName(accountType.resPackageName,
+                            accountType.getViewGroupActivity());
+                    startActivity(intent);
                 }
             });
         } else if (mGroupSourceView != null) {
@@ -431,4 +436,8 @@
     public void closeActivityAfterDelete(boolean closeActivity) {
         mCloseActivityAfterDelete = closeActivity;
     }
+
+    public long getGroupId() {
+        return mGroupId;
+    }
 }
diff --git a/src/com/android/contacts/interactions/PhoneNumberInteraction.java b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
index 9442f5e..918dac0 100644
--- a/src/com/android/contacts/interactions/PhoneNumberInteraction.java
+++ b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
@@ -24,11 +24,11 @@
 import com.android.contacts.model.AccountType.StringInflater;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.DataKind;
+import com.android.i18n.phonenumbers.NumberParseException;
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
+import com.android.i18n.phonenumbers.PhoneNumberUtil.MatchType;
+import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
 import com.google.common.annotations.VisibleForTesting;
-import com.google.i18n.phonenumbers.NumberParseException;
-import com.google.i18n.phonenumbers.PhoneNumberUtil;
-import com.google.i18n.phonenumbers.PhoneNumberUtil.MatchType;
-import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
 
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -89,6 +89,18 @@
         long type;
         String label;
 
+        public PhoneItem() {
+        }
+
+        private PhoneItem(Parcel in) {
+            this.id          = in.readLong();
+            this.phoneNumber = in.readString();
+            this.accountType = in.readString();
+            this.dataSet     = in.readString();
+            this.type        = in.readLong();
+            this.label       = in.readString();
+        }
+
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeLong(id);
             dest.writeString(phoneNumber);
@@ -129,6 +141,17 @@
         public String toString() {
             return phoneNumber;
         }
+
+        public static final Parcelable.Creator<PhoneItem> CREATOR
+                = new Parcelable.Creator<PhoneItem>() {
+            public PhoneItem createFromParcel(Parcel in) {
+                return new PhoneItem(in);
+            }
+
+            public PhoneItem[] newArray(int size) {
+                return new PhoneItem[size];
+            }
+        };
     }
 
     /**
diff --git a/src/com/android/contacts/list/ContactTileAdapter.java b/src/com/android/contacts/list/ContactTileAdapter.java
index 7a293b3..37ccded 100644
--- a/src/com/android/contacts/list/ContactTileAdapter.java
+++ b/src/com/android/contacts/list/ContactTileAdapter.java
@@ -73,6 +73,7 @@
     private int mPhoneNumberLabelIndex;
 
     private boolean mIsQuickContactEnabled = false;
+    private final int mPaddingInPixels;
 
     /**
      * Configures the adapter to filter and display contacts using different view types.
@@ -116,6 +117,10 @@
         mColumnCount = (displayType == DisplayType.FREQUENT_ONLY ? 1 : numCols);
         mDisplayType = displayType;
 
+        // Converting padding in dips to padding in pixels
+        mPaddingInPixels = mContext.getResources()
+                .getDimensionPixelOffset(R.dimen.contact_tile_divider_padding);
+
         bindColumnIndices();
     }
 
@@ -406,7 +411,7 @@
     }
     @Override
     public int getViewTypeCount() {
-        return ViewTypes.MAX_VIEW_COUNT;
+        return ViewTypes.COUNT;
     }
 
     @Override
@@ -498,10 +503,21 @@
             contactTile.setClickable(entry != null);
             contactTile.loadFromContact(entry);
 
-            contactTile.setVerticalDividerVisibility(
-                    childIndex >= mColumnCount - 1 ? View.GONE : View.VISIBLE);
-            contactTile.setHorizontalDividerVisibility(
-                    isLastRow ? View.GONE : View.VISIBLE);
+            switch (mItemViewType) {
+                case ViewTypes.STARRED_WITH_SECONDARY_ACTION:
+                case ViewTypes.STARRED:
+                    // Setting divider visibilities
+                    contactTile.setPadding(0, 0,
+                            childIndex >= mColumnCount - 1 ? 0 : mPaddingInPixels,
+                            isLastRow ? 0 : mPaddingInPixels);
+                    break;
+                case ViewTypes.FREQUENT:
+                    contactTile.setHorizontalDividerVisibility(
+                            isLastRow ? View.GONE : View.VISIBLE);
+                    break;
+                default:
+                    break;
+            }
         }
     }
 
@@ -519,7 +535,7 @@
     }
 
     private static class ViewTypes {
-        public static final int MAX_VIEW_COUNT = 4;
+        public static final int COUNT = 4;
         public static final int STARRED = 0;
         public static final int DIVIDER = 1;
         public static final int FREQUENT = 2;
diff --git a/src/com/android/contacts/list/ContactTileView.java b/src/com/android/contacts/list/ContactTileView.java
index ef9c2fb..25edd7f 100644
--- a/src/com/android/contacts/list/ContactTileView.java
+++ b/src/com/android/contacts/list/ContactTileView.java
@@ -27,7 +27,6 @@
 import android.util.Log;
 import android.view.View;
 import android.widget.FrameLayout;
-import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.QuickContactBadge;
 import android.widget.TextView;
@@ -40,17 +39,15 @@
 
     private Uri mLookupUri;
     private ImageView mPhoto;
-    private ImageView mPresence;
     private QuickContactBadge mQuickContact;
     private TextView mName;
     private TextView mStatus;
     private TextView mPhoneLabel;
     private TextView mPhoneNumber;
     private ContactPhotoManager mPhotoManager = null;
-    private ImageButton mPushState;
-    private Listener mListener;
-    private View mVerticalDivider;
+    private View mPushState;
     private View mHorizontalDivider;
+    private Listener mListener;
 
     public ContactTileView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -63,12 +60,10 @@
 
         mQuickContact = (QuickContactBadge) findViewById(R.id.contact_tile_quick);
         mPhoto = (ImageView) findViewById(R.id.contact_tile_image);
-        mPresence = (ImageView) findViewById(R.id.contact_tile_presence);
         mStatus = (TextView) findViewById(R.id.contact_tile_status);
         mPhoneLabel = (TextView) findViewById(R.id.contact_tile_phone_type);
         mPhoneNumber = (TextView) findViewById(R.id.contact_tile_phone_number);
-        mPushState = (ImageButton) findViewById(R.id.contact_tile_push_state);
-        mVerticalDivider = findViewById(R.id.contact_tile_vertical_divider);
+        mPushState = findViewById(R.id.contact_tile_push_state);
         mHorizontalDivider = findViewById(R.id.contact_tile_horizontal_divider);
 
         OnClickListener listener = new OnClickListener() {
@@ -100,23 +95,25 @@
             mName.setText(entry.name);
             mLookupUri = entry.lookupKey;
 
-            int presenceDrawableResId = (entry.presence == null ? 0 :
-                    StatusUpdates.getPresenceIconResourceId(entry.presence));
-
-            if (mPresence != null) {
-                mPresence.setBackgroundResource(presenceDrawableResId);
-            }
-
             if (mStatus != null) {
                 String statusText;
                 if (entry.presence == null) {
-                    statusText = null;
+                    mStatus.setVisibility(View.GONE);
                 } else {
                     statusText =
                           (entry.status != null ? entry.status :
                           ContactStatusUtil.getStatusString(mContext, entry.presence));
+                    mStatus.setText(statusText);
+                    int presenceDrawableResId = (entry.presence == null ? 0 :
+                            StatusUpdates.getPresenceIconResourceId(entry.presence));
+                    if (presenceDrawableResId != 0) {
+                        Log.i(TAG, "iconId = " + presenceDrawableResId);
+                        mStatus.setCompoundDrawablesWithIntrinsicBounds(
+                                getResources().getDrawable(presenceDrawableResId),
+                                null, null, null);
+                    }
+                    mStatus.setVisibility(View.VISIBLE);
                 }
-                mStatus.setText(statusText);
             }
 
             if (mPhoneLabel != null) {
@@ -128,21 +125,19 @@
                 mPhoneNumber.setText(entry.phoneNumber);
             }
 
-            if (mQuickContact != null) {
-                mQuickContact.assignContactUri(mLookupUri);
-                mQuickContact.setImageBitmap(null);
-            } else {
-                mPhoto.setImageBitmap(null);
-            }
-
             setVisibility(View.VISIBLE);
 
             if (mPhotoManager != null) {
-                if (mQuickContact != null){
-                    mPhotoManager.loadPhoto(mQuickContact, entry.photoUri);
-                } else {
+                if (mPhoto != null) {
                     mPhotoManager.loadPhoto(mPhoto, entry.photoUri);
-                }
+
+                    if (mQuickContact != null) {
+                        mQuickContact.assignContactUri(mLookupUri);
+                    }
+                } else if (mQuickContact != null) {
+                        mQuickContact.assignContactUri(mLookupUri);
+                        mPhotoManager.loadPhoto(mQuickContact, entry.photoUri);
+                    }
 
             } else {
                 Log.w(TAG, "contactPhotoManager not set");
@@ -152,18 +147,14 @@
         }
     }
 
-    public void setVerticalDividerVisibility(int visibility) {
-        if (mVerticalDivider != null) mVerticalDivider.setVisibility(visibility);
+    public void setListener(Listener listener) {
+        mListener = listener;
     }
 
     public void setHorizontalDividerVisibility(int visibility) {
         if (mHorizontalDivider != null) mHorizontalDivider.setVisibility(visibility);
     }
 
-    public void setListener(Listener listener) {
-        mListener = listener;
-    }
-
     public Uri getLookupUri() {
         return mLookupUri;
     }
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index a78185f..95216e6 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -123,6 +123,21 @@
         return null;
     }
 
+    /** Returns an optional Activity string that can be used to view the group. */
+    public String getViewGroupActivity() {
+        return null;
+    }
+
+    /** Returns an optional Activity string that can be used to view the stream item. */
+    public String getViewStreamItemActivity() {
+        return null;
+    }
+
+    /** Returns an optional Activity string that can be used to view the stream item photo. */
+    public String getViewStreamItemPhotoActivity() {
+        return null;
+    }
+
     public CharSequence getDisplayLabel(Context context) {
         return getResourceText(context, summaryResPackageName, titleRes, accountType);
     }
@@ -130,7 +145,7 @@
     /**
      * @return resource ID for the "invite contact" action label, or -1 if not defined.
      */
-    protected int getInviteContactActionResId(Context conext) {
+    protected int getInviteContactActionResId(Context context) {
         return -1;
     }
 
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index b517c2c..04b3fa8 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -16,12 +16,12 @@
 
 package com.android.contacts.model;
 
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
 import com.android.internal.util.Objects;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 import com.google.android.collect.Sets;
 import com.google.common.annotations.VisibleForTesting;
-import com.google.i18n.phonenumbers.PhoneNumberUtil;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/ExternalAccountType.java
index 5dd4d3b..791f0ae 100644
--- a/src/com/android/contacts/model/ExternalAccountType.java
+++ b/src/com/android/contacts/model/ExternalAccountType.java
@@ -58,6 +58,10 @@
     private static final String ATTR_INVITE_CONTACT_ACTIVITY = "inviteContactActivity";
     private static final String ATTR_INVITE_CONTACT_ACTION_LABEL = "inviteContactActionLabel";
     private static final String ATTR_VIEW_CONTACT_NOTIFY_SERVICE = "viewContactNotifyService";
+    private static final String ATTR_VIEW_GROUP_ACTIVITY = "viewGroupActivity";
+    private static final String ATTR_VIEW_STREAM_ITEM_ACTIVITY = "viewStreamItemActivity";
+    private static final String ATTR_VIEW_STREAM_ITEM_PHOTO_ACTIVITY =
+            "viewStreamItemPhotoActivity";
     private static final String ATTR_DATA_SET = "dataSet";
     private static final String ATTR_EXTENSION_PACKAGE_NAMES = "extensionPackageNames";
 
@@ -73,6 +77,9 @@
     private String mInviteContactActivity;
     private String mInviteActionLabelAttribute;
     private String mViewContactNotifyService;
+    private String mViewGroupActivity;
+    private String mViewStreamItemActivity;
+    private String mViewStreamItemPhotoActivity;
     private List<String> mExtensionPackageNames;
     private int mInviteActionLabelResId;
     private String mAccountTypeLabelAttribute;
@@ -156,6 +163,21 @@
     }
 
     @Override
+    public String getViewGroupActivity() {
+        return mViewGroupActivity;
+    }
+
+    @Override
+    public String getViewStreamItemActivity() {
+        return mViewStreamItemActivity;
+    }
+
+    @Override
+    public String getViewStreamItemPhotoActivity() {
+        return mViewStreamItemPhotoActivity;
+    }
+
+    @Override
     public List<String> getExtensionPackageNames() {
         return mExtensionPackageNames;
     }
@@ -202,6 +224,12 @@
                     mInviteActionLabelAttribute = value;
                 } else if (ATTR_VIEW_CONTACT_NOTIFY_SERVICE.equals(attr)) {
                     mViewContactNotifyService = value;
+                } else if (ATTR_VIEW_GROUP_ACTIVITY.equals(attr)) {
+                    mViewGroupActivity = value;
+                } else if (ATTR_VIEW_STREAM_ITEM_ACTIVITY.equals(attr)) {
+                    mViewStreamItemActivity = value;
+                } else if (ATTR_VIEW_STREAM_ITEM_PHOTO_ACTIVITY.equals(attr)) {
+                    mViewStreamItemPhotoActivity = value;
                 } else if (ATTR_DATA_SET.equals(attr)) {
                     dataSet = value;
                 } else if (ATTR_EXTENSION_PACKAGE_NAMES.equals(attr)) {
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
index 98a843e..682f700 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
@@ -18,8 +18,9 @@
 
 import com.android.contacts.ContactLoader;
 import com.android.contacts.R;
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.util.ContactBadgeUtil;
-import com.android.contacts.util.DataStatus;
 import com.android.contacts.util.StreamItemEntry;
 
 import android.app.PendingIntent;
@@ -33,8 +34,8 @@
 import android.graphics.BitmapFactory;
 import android.graphics.Typeface;
 import android.net.Uri;
-import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.QuickContact;
+import android.provider.ContactsContract.StreamItems;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 import android.text.style.AbsoluteSizeSpan;
@@ -44,7 +45,6 @@
 import android.view.View;
 import android.widget.RemoteViews;
 
-import java.util.HashMap;
 import java.util.List;
 
 public class SocialWidgetProvider extends AppWidgetProvider {
@@ -214,10 +214,15 @@
             views.setTextViewText(R.id.name_and_snippet, sb);
             views.setViewVisibility(R.id.name, View.GONE);
             views.setViewVisibility(R.id.name_and_snippet, View.VISIBLE);
-            if (!TextUtils.isEmpty(streamItem.getAction())
-                    && !TextUtils.isEmpty(streamItem.getActionUri())) {
-                final Intent intent = new Intent(streamItem.getAction(),
-                        Uri.parse(streamItem.getActionUri()));
+            final AccountTypeManager manager = AccountTypeManager.getInstance(context);
+            final AccountType accountType =
+                    manager.getAccountType(streamItem.getAccountType(), streamItem.getDataSet());
+            if (accountType.getViewStreamItemActivity() != null) {
+                final Uri uri = ContentUris.withAppendedId(StreamItems.CONTENT_URI,
+                        streamItem.getId());
+                final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+                intent.setClassName(accountType.resPackageName,
+                        accountType.getViewStreamItemActivity());
                 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 2f37b4c..806264d 100644
--- a/src/com/android/contacts/util/ContactBadgeUtil.java
+++ b/src/com/android/contacts/util/ContactBadgeUtil.java
@@ -16,14 +16,12 @@
 
 package com.android.contacts.util;
 
-import com.android.contacts.ContactLoader;
 import com.android.contacts.R;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.text.TextUtils;
@@ -57,9 +55,9 @@
 
         String labelDisplayValue = null;
 
-        final Integer statusLabel = streamItem.getLabelRes();
+        final String statusLabelRes = streamItem.getLabelRes();
         final String statusResPackage = streamItem.getResPackage();
-        if (statusLabel  != null) {
+        if (statusLabelRes  != null) {
             Resources resources;
             if (TextUtils.isEmpty(statusResPackage)) {
                 resources = context.getResources();
@@ -75,11 +73,13 @@
             }
 
             if (resources != null) {
-                try {
-                    labelDisplayValue = resources.getString(statusLabel.intValue());
-                } catch (NotFoundException e) {
-                    Log.w(TAG, "Contact status update resource not found: " + statusResPackage + "@"
-                            + statusLabel.intValue());
+                final int resId = resources.getIdentifier(statusLabelRes, "string",
+                        statusResPackage);
+                if (resId == 0) {
+                    Log.w(TAG, "Contact status update resource not found: " + statusLabelRes +
+                            " in " + statusResPackage);
+                } else {
+                    labelDisplayValue = resources.getString(resId);
                 }
             }
         }
diff --git a/src/com/android/contacts/util/StreamItemEntry.java b/src/com/android/contacts/util/StreamItemEntry.java
index db15e3a..5959c46 100644
--- a/src/com/android/contacts/util/StreamItemEntry.java
+++ b/src/com/android/contacts/util/StreamItemEntry.java
@@ -37,26 +37,29 @@
     private final String mText;
     private final String mComments;
     private final long mTimestamp;
-    private final String mAction;
-    private final String mActionUri;
+    private final String mAccountType;
+    private final String mAccountName;
+    private final String mDataSet;
 
     // Package references for label and icon resources.
     private final String mResPackage;
-    private final int mIconRes;
-    private final int mLabelRes;
+    private final String mIconRes;
+    private final String mLabelRes;
 
     // Photos associated with this stream item.
     private List<StreamItemPhotoEntry> mPhotos;
 
     @NeededForTesting
-    public StreamItemEntry(long id, String text, String comments, long timestamp, String action,
-            String actionUri, String resPackage, int iconRes, int labelRes) {
+    public StreamItemEntry(long id, String text, String comments, long timestamp,
+            String accountType, String accountName, String dataSet, String resPackage,
+            String iconRes, String labelRes) {
         mId = id;
         mText = text;
         mComments = comments;
         mTimestamp = timestamp;
-        mAction = action;
-        mActionUri = actionUri;
+        mAccountType = accountType;
+        mAccountName = accountName;
+        mDataSet = dataSet;
         mResPackage = resPackage;
         mIconRes = iconRes;
         mLabelRes = labelRes;
@@ -70,11 +73,12 @@
         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);
+        mAccountType = getString(cursor, StreamItems.ACCOUNT_TYPE);
+        mAccountName = getString(cursor, StreamItems.ACCOUNT_NAME);
+        mDataSet = getString(cursor, StreamItems.DATA_SET);
         mResPackage = getString(cursor, StreamItems.RES_PACKAGE);
-        mIconRes = getInt(cursor, StreamItems.RES_ICON, -1);
-        mLabelRes = getInt(cursor, StreamItems.RES_LABEL, -1);
+        mIconRes = getString(cursor, StreamItems.RES_ICON);
+        mLabelRes = getString(cursor, StreamItems.RES_LABEL);
         mPhotos = new ArrayList<StreamItemPhotoEntry>();
     }
 
@@ -103,23 +107,27 @@
         return mTimestamp;
     }
 
-    public String getAction() {
-        return mAction;
+    public String getAccountType() {
+        return mAccountType;
     }
 
-    public String getActionUri() {
-        return mActionUri;
+    public String getAccountName() {
+        return mAccountName;
+    }
+
+    public String getDataSet() {
+        return mDataSet;
     }
 
     public String getResPackage() {
         return mResPackage;
     }
 
-    public int getIconRes() {
+    public String getIconRes() {
         return mIconRes;
     }
 
-    public int getLabelRes() {
+    public String getLabelRes() {
         return mLabelRes;
     }
 
@@ -132,11 +140,6 @@
         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
index 6527454..ad69e5c 100644
--- a/src/com/android/contacts/util/StreamItemPhotoEntry.java
+++ b/src/com/android/contacts/util/StreamItemPhotoEntry.java
@@ -33,11 +33,9 @@
     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) {
+            int height, int width, int fileSize) {
         mId = id;
         mSortIndex = sortIndex;
         mPhotoFileId = photoFileId;
@@ -45,8 +43,6 @@
         mHeight = height;
         mWidth = width;
         mFileSize = fileSize;
-        mAction = action;
-        mActionUri = actionUri;
     }
 
     public StreamItemPhotoEntry(Cursor cursor) {
@@ -59,8 +55,6 @@
         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() {
@@ -91,14 +85,6 @@
         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
diff --git a/tests/src/com/android/contacts/ContactLoaderTest.java b/tests/src/com/android/contacts/ContactLoaderTest.java
index a731f24..3560ce1 100644
--- a/tests/src/com/android/contacts/ContactLoaderTest.java
+++ b/tests/src/com/android/contacts/ContactLoaderTest.java
@@ -16,7 +16,12 @@
 
 package com.android.contacts;
 
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.BaseAccountType;
+import com.android.contacts.test.InjectedServices;
 import com.android.contacts.tests.mocks.ContactsMockContext;
+import com.android.contacts.tests.mocks.MockAccountTypeManager;
 import com.android.contacts.tests.mocks.MockContentProvider;
 
 import android.content.ContentUris;
@@ -36,18 +41,31 @@
  */
 @LargeTest
 public class ContactLoaderTest extends LoaderTestCase {
-    ContactsMockContext mMockContext;
-    MockContentProvider mContactsProvider;
+    private ContactsMockContext mMockContext;
+    private MockContentProvider mContactsProvider;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         mMockContext = new ContactsMockContext(getContext());
         mContactsProvider = mMockContext.getContactsProvider();
+
+        InjectedServices services = new InjectedServices();
+        AccountType accountType = new BaseAccountType();
+        accountType.accountType = "mockAccountType";
+
+        AccountWithDataSet account =
+                new AccountWithDataSet("mockAccountName", "mockAccountType", null);
+
+        mMockContext.setMockAccountTypeManager(
+                new MockAccountTypeManager(
+                        new AccountType[] { accountType }, new AccountWithDataSet[] { account }));
     }
 
     @Override
     protected void tearDown() throws Exception {
+        mMockContext = null;
+        mContactsProvider = null;
         super.tearDown();
     }
 
diff --git a/tests/src/com/android/contacts/activities/CallLogActivityTests.java b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
index 070941c..5926fd9 100644
--- a/tests/src/com/android/contacts/activities/CallLogActivityTests.java
+++ b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
@@ -18,10 +18,12 @@
 
 import com.android.contacts.CallDetailActivity;
 import com.android.contacts.R;
+import com.android.contacts.calllog.CallLogAdapter;
 import com.android.contacts.calllog.CallLogFragment;
-import com.android.contacts.calllog.CallLogFragment.CallLogQuery;
-import com.android.contacts.calllog.CallLogFragment.ContactInfo;
 import com.android.contacts.calllog.CallLogListItemViews;
+import com.android.contacts.calllog.CallLogQuery;
+import com.android.contacts.calllog.CallLogQueryTestUtils;
+import com.android.contacts.calllog.ContactInfo;
 import com.android.contacts.calllog.IntentProvider;
 import com.android.internal.telephony.CallerInfo;
 
@@ -61,17 +63,6 @@
 @LargeTest
 public class CallLogActivityTests
         extends ActivityInstrumentationTestCase2<CallLogActivity> {
-    private static final String[] EXTENDED_CALL_LOG_PROJECTION = new String[] {
-            Calls._ID,
-            Calls.NUMBER,
-            Calls.DATE,
-            Calls.DURATION,
-            Calls.TYPE,
-            Calls.COUNTRY_ISO,
-            Calls.VOICEMAIL_URI,
-            Calls.GEOCODED_LOCATION,
-            CallLogFragment.CallLogQuery.SECTION_NAME,
-    };
     private static final int RAND_DURATION = -1;
     private static final long NOW = -1L;
 
@@ -95,7 +86,7 @@
     private CallLogActivity mActivity;
     private CallLogFragment mFragment;
     private FrameLayout mParentView;
-    private CallLogFragment.CallLogAdapter mAdapter;
+    private CallLogAdapter mAdapter;
     private String mVoicemail;
 
     // In memory array to hold the rows corresponding to the 'calls' table.
@@ -132,7 +123,7 @@
         mAdapter.disableRequestProcessingForTest();
         mAdapter.stopRequestProcessing();
         mParentView = new FrameLayout(mActivity);
-        mCursor = new MatrixCursor(EXTENDED_CALL_LOG_PROJECTION);
+        mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
         buildIconMap();
     }
 
@@ -487,27 +478,19 @@
      * @param type Either Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
      */
     private void insert(String number, long date, int duration, int type) {
-        MatrixCursor.RowBuilder row = mCursor.newRow();
-        row.add(mIndex);
-        mIndex ++;
-        row.add(number);
-        if (NOW == date) {
-            row.add(new Date().getTime());
-        } else {
-            row.add(date);
-        }
-        if (duration < 0) {
-            duration = mRnd.nextInt(10 * 60);  // 0 - 10 minutes random.
-        }
-        row.add(duration);  // duration
+        Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+        values[CallLogQuery.ID] = mIndex;
+        values[CallLogQuery.NUMBER] = number;
+        values[CallLogQuery.DATE] = date == NOW ? new Date().getTime() : date;
+        values[CallLogQuery.DURATION] = duration < 0 ? mRnd.nextInt(10 * 60) : duration;
         if (mVoicemail != null && mVoicemail.equals(number)) {
             assertEquals(Calls.OUTGOING_TYPE, type);
         }
-        row.add(type);  // type
-        row.add(TEST_COUNTRY_ISO);  // country ISO
-        row.add(null);  // voicemail_uri
-        row.add(null);  // geocoded_location
-        row.add(CallLogFragment.CallLogQuery.SECTION_OLD_ITEM);  // section
+        values[CallLogQuery.CALL_TYPE] = type;
+        values[CallLogQuery.COUNTRY_ISO] = TEST_COUNTRY_ISO;
+        values[CallLogQuery.SECTION] = CallLogQuery.SECTION_OLD_ITEM;
+        mCursor.addRow(values);
+        ++mIndex;
     }
 
     /**
@@ -518,27 +501,19 @@
      * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
      */
     private void insertVoicemail(String number, long date, int duration) {
-        MatrixCursor.RowBuilder row = mCursor.newRow();
+        Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+        values[CallLogQuery.ID] = mIndex;
+        values[CallLogQuery.NUMBER] = number;
+        values[CallLogQuery.DATE] = date == NOW ? new Date().getTime() : date;
+        values[CallLogQuery.DURATION] = duration < 0 ? mRnd.nextInt(10 * 60) : duration;
+        values[CallLogQuery.CALL_TYPE] = Calls.VOICEMAIL_TYPE;
+        values[CallLogQuery.COUNTRY_ISO] = TEST_COUNTRY_ISO;
         // Must have the same index as the row.
-        Uri voicemailUri =
+        values[CallLogQuery.VOICEMAIL_URI] =
                 ContentUris.withAppendedId(VoicemailContract.Voicemails.CONTENT_URI, mIndex);
-        row.add(mIndex);
-        mIndex ++;
-        row.add(number);
-        if (NOW == date) {
-            row.add(new Date().getTime());
-        } else {
-            row.add(date);
-        }
-        if (duration < 0) {
-            duration = mRnd.nextInt(10 * 60);  // 0 - 10 minutes random.
-        }
-        row.add(duration);  // duration
-        row.add(Calls.VOICEMAIL_TYPE);  // type
-        row.add(TEST_COUNTRY_ISO);  // country ISO
-        row.add(voicemailUri);  // voicemail_uri
-        row.add(null);  // geocoded_location
-        row.add(CallLogFragment.CallLogQuery.SECTION_OLD_ITEM);  // section
+        values[CallLogQuery.SECTION] = CallLogQuery.SECTION_OLD_ITEM;
+        mCursor.addRow(values);
+        ++mIndex;
     }
 
     /**
diff --git a/tests/src/com/android/contacts/calllog/CallLogGroupBuilderTest.java b/tests/src/com/android/contacts/calllog/CallLogGroupBuilderTest.java
index 8a7e946..31ad548 100644
--- a/tests/src/com/android/contacts/calllog/CallLogGroupBuilderTest.java
+++ b/tests/src/com/android/contacts/calllog/CallLogGroupBuilderTest.java
@@ -18,8 +18,6 @@
 
 import static com.google.android.collect.Lists.newArrayList;
 
-import com.android.contacts.calllog.CallLogFragment.CallLogQuery;
-
 import android.database.MatrixCursor;
 import android.provider.CallLog.Calls;
 import android.test.AndroidTestCase;
@@ -171,7 +169,7 @@
 
     /** Creates (or recreates) the cursor used to store the call log content for the tests. */
     private void createCursor() {
-        mCursor = new MatrixCursor(CallLogFragment.CallLogQuery.EXTENDED_PROJECTION);
+        mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
     }
 
     /** Clears the content of the {@link FakeGroupCreator} used in the tests. */
@@ -223,9 +221,12 @@
             throw new IllegalArgumentException("not an item section: " + section);
         }
         mCursor.moveToNext();
-        mCursor.addRow(new Object[]{
-                mCursor.getPosition(), number, 0L, 0L, type, "", "", "", section
-        });
+        Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+        values[CallLogQuery.ID] = mCursor.getPosition();
+        values[CallLogQuery.NUMBER] = number;
+        values[CallLogQuery.CALL_TYPE] = type;
+        values[CallLogQuery.SECTION] = section;
+        mCursor.addRow(values);
     }
 
     /** Adds the old section header to the call log. */
@@ -245,7 +246,10 @@
             throw new IllegalArgumentException("not a header section: " + section);
         }
         mCursor.moveToNext();
-        mCursor.addRow(new Object[]{ mCursor.getPosition(), "", 0L, 0L, 0, "", "", "", section });
+        Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
+        values[CallLogQuery.ID] = mCursor.getPosition();
+        values[CallLogQuery.SECTION] = section;
+        mCursor.addRow(values);
     }
 
     /** Asserts that the group matches the given values. */
@@ -272,7 +276,7 @@
     }
 
     /** Fake implementation of a GroupCreator which stores the created groups in a member field. */
-    private static class FakeGroupCreator implements CallLogFragment.GroupCreator {
+    private static class FakeGroupCreator implements CallLogGroupBuilder.GroupCreator {
         /** The list of created groups. */
         public final List<GroupSpec> groups = newArrayList();
 
diff --git a/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java b/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java
new file mode 100644
index 0000000..0e1952a
--- /dev/null
+++ b/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java
@@ -0,0 +1,37 @@
+/*
+ * 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.calllog;
+
+import static junit.framework.Assert.assertEquals;
+import junit.framework.Assert;
+
+/**
+ * Helper class to create test values for {@link CallLogQuery}.
+ */
+public class CallLogQueryTestUtils {
+    public static Object[] createTestValues() {
+        Object[] values = new Object[]{ -1L, "", 0L, 0L, 0, "", "", "", null, 0, null };
+        assertEquals(CallLogQuery._PROJECTION.length, values.length);
+        return values;
+    }
+
+    public static Object[] createTestExtendedValues() {
+        Object[] values = new Object[]{ -1L, "", 0L, 0L, 0, "", "", "", null, 0, null, 0 };
+        Assert.assertEquals(CallLogQuery.EXTENDED_PROJECTION.length, values.length);
+        return values;
+    }
+}
diff --git a/tests/src/com/android/contacts/detail/StreamItemAdapterTest.java b/tests/src/com/android/contacts/detail/StreamItemAdapterTest.java
index 99ae834..d862d6e 100644
--- a/tests/src/com/android/contacts/detail/StreamItemAdapterTest.java
+++ b/tests/src/com/android/contacts/detail/StreamItemAdapterTest.java
@@ -20,12 +20,14 @@
 import com.android.contacts.util.StreamItemEntryBuilder;
 import com.google.common.collect.Lists;
 
-import android.content.Intent;
 import android.test.AndroidTestCase;
 import android.view.View;
 
 import java.util.ArrayList;
 
+// TODO: We should have tests for action, but that requires a mock sync-adapter that specifies
+// an action or doesn't
+
 /**
  * Unit tests for {@link StreamItemAdapter}.
  */
@@ -51,30 +53,13 @@
     public void testGetCount_Empty() {
         mAdapter.setStreamItems(createStreamItemList(0));
         // There is actually one view: the header.
-        assertEquals(1, mAdapter.getCount());
+        assertEquals(2, mAdapter.getCount());
     }
 
     public void testGetCount_NonEmpty() {
         mAdapter.setStreamItems(createStreamItemList(3));
         // There is one extra view: the header.
-        assertEquals(4, mAdapter.getCount());
-    }
-
-    public void testGetView_WithAction() {
-        StreamItemEntry streamItem = createStreamItemWithAction();
-        mAdapter.setStreamItems(Lists.newArrayList(streamItem));
-        mView = mAdapter.getView(1, null, null);
-        assertStreamItemViewHasTag(streamItem);
-        assertStreamItemViewHasOnClickListener();
-        assertStreamItemViewFocusable();
-    }
-
-    public void testGetView_WithoutAction() {
-        mAdapter.setStreamItems(Lists.newArrayList(createStreamItemWithoutAction()));
-        mView = mAdapter.getView(1, null, null);
-        assertStreamItemViewHasNoTag();
-        assertStreamItemViewHasNoOnClickListener();
-        assertStreamItemViewNotFocusable();
+        assertEquals(5, mAdapter.getCount());
     }
 
     public void testGetView_Header() {
@@ -91,22 +76,6 @@
                 "text #" + mCreateStreamItemEntryBuilderCounter++);
     }
 
-    /** Returns a stream item with an action and action URI set. */
-    private StreamItemEntry createStreamItemWithAction() {
-        return createStreamItemEntryBuilder()
-                .setAction(Intent.ACTION_VIEW)
-                .setActionUri("http://www.google.com")
-                .build();
-    }
-
-    /** Returns a stream item without an action and action URI set. */
-    private StreamItemEntry createStreamItemWithoutAction() {
-        return createStreamItemEntryBuilder()
-                .setAction(null)
-                .setActionUri(null)
-                .build();
-    }
-
     /** Creates a list containing the given number of {@link StreamItemEntry}s. */
     private ArrayList<StreamItemEntry> createStreamItemList(int count) {
         ArrayList<StreamItemEntry> list = Lists.newArrayList();
diff --git a/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java b/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
index 93ea4f4..2f959f4 100644
--- a/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
+++ b/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
@@ -16,6 +16,8 @@
 
 package com.android.contacts.tests.mocks;
 
+import com.android.contacts.model.AccountTypeManager;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -32,13 +34,11 @@
  * to mock content providers.
  */
 public class ContactsMockContext extends ContextWrapper {
-
-    private static final String TAG = "ContactsMockContext";
-
     private ContactsMockPackageManager mPackageManager;
     private MockContentResolver mContentResolver;
     private MockContentProvider mContactsProvider;
     private MockContentProvider mSettingsProvider;
+    private MockAccountTypeManager mMockAccountTypeManager;
     private Intent mIntentForStartActivity;
 
     public ContactsMockContext(Context base) {
@@ -53,6 +53,10 @@
         mContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
     }
 
+    public void setMockAccountTypeManager(MockAccountTypeManager mockAccountTypeManager) {
+        mMockAccountTypeManager = mockAccountTypeManager;
+    }
+
     @Override
     public ContentResolver getContentResolver() {
         return mContentResolver;
@@ -93,4 +97,12 @@
         mContactsProvider.verify();
         mSettingsProvider.verify();
     }
+
+    @Override
+    public Object getSystemService(String name) {
+        if (AccountTypeManager.ACCOUNT_TYPE_SERVICE.equals(name)) {
+           return mMockAccountTypeManager;
+        }
+        return super.getSystemService(name);
+    }
 }
diff --git a/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java b/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java
index e27c767..d6e95ef 100644
--- a/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java
+++ b/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java
@@ -83,15 +83,15 @@
             "<i>24567</i> <font color='blue' size='+1'><b>likes</b></font>"
     };
 
-    private Integer[] labelIds = new Integer[] {
-            R.string.attribution_google_plus,
-            R.string.attribution_google_talk,
-            R.string.attribution_flicker,
-            R.string.attribution_twitter
+    private String[] labelResources = new String[] {
+            "attribution_google_plus",
+            "attribution_google_talk",
+            "attribution_flicker",
+            "attribution_twitter"
     };
 
-    public Integer[] iconIds = new Integer[] {
-            R.drawable.default_icon,
+    public String[] iconResources = new String[] {
+            "default_icon"
     };
 
     // Photos to randomly select from.
@@ -264,10 +264,10 @@
                 + (includeAction ? " [a]" : ""));
         if (includeAttribution) {
             values.put(StreamItems.RES_PACKAGE, "com.android.contacts.tests");
-            int sourceIndex = randInt(labelIds.length);
-            values.put(StreamItems.RES_LABEL, labelIds[sourceIndex]);
-            if (sourceIndex < iconIds.length) {
-                values.put(StreamItems.RES_ICON, iconIds[sourceIndex]);
+            int sourceIndex = randInt(labelResources.length);
+            values.put(StreamItems.RES_LABEL, labelResources[sourceIndex]);
+            if (sourceIndex < iconResources.length) {
+                values.put(StreamItems.RES_ICON, iconResources[sourceIndex]);
             }
         }
         if (includeComments) {
@@ -280,10 +280,6 @@
                 System.currentTimeMillis() - randInt(360000000));
         values.put(RawContacts.ACCOUNT_TYPE, accountType);
         values.put(RawContacts.ACCOUNT_NAME, accountName);
-        if (includeAction) {
-            values.put(StreamItems.ACTION, Intent.ACTION_VIEW);
-            values.put(StreamItems.ACTION_URI, getGoogleSearchUri(place));
-        }
         return values;
     }
 
@@ -295,20 +291,9 @@
         values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(imageIndex));
         values.put(RawContacts.ACCOUNT_TYPE, accountType);
         values.put(RawContacts.ACCOUNT_NAME, accountName);
-        if (imageIndex < imageStrings.length) {
-            values.put(StreamItemPhotos.ACTION, Intent.ACTION_VIEW);
-            String queryTerm = imageStrings[imageIndex];
-            values.put(StreamItemPhotos.ACTION_URI, getGoogleSearchUri(queryTerm));
-
-        }
         return values;
     }
 
-    /** Returns the URI of the Google search results page for the given query. */
-    private String getGoogleSearchUri(String query) {
-        return "http://www.google.com/search?q=" + query.replace(" ", "+");
-    }
-
     private <T> T pickRandom(T[] from) {
         return from[randInt(from.length)];
     }
diff --git a/tests/src/com/android/contacts/util/StreamItemEntryBuilder.java b/tests/src/com/android/contacts/util/StreamItemEntryBuilder.java
index 8a17b4a..319ba48 100644
--- a/tests/src/com/android/contacts/util/StreamItemEntryBuilder.java
+++ b/tests/src/com/android/contacts/util/StreamItemEntryBuilder.java
@@ -24,36 +24,42 @@
     private String mText;
     private String mComment;
     private long mTimestamp;
-    private String mAction;
-    private String mActionUri;
+    private String mAccountType;
+    private String mAccountName;
+    private String mDataSet;
     private String mResPackage;
-    private int mIconRes;
-    private int mLabelRes;
+    private String mIconRes;
+    private String mLabelRes;
 
     public StreamItemEntryBuilder() {}
 
-    public StreamItemEntryBuilder setText(String text) {
-        mText = text;
+    public StreamItemEntryBuilder setText(String value) {
+        mText = value;
         return this;
     }
 
-    public StreamItemEntryBuilder setComment(String comment) {
-        mComment = comment;
+    public StreamItemEntryBuilder setComment(String value) {
+        mComment = value;
         return this;
     }
 
-    public StreamItemEntryBuilder setAction(String action) {
-        mAction = action;
+    public StreamItemEntryBuilder setAccountType(String value) {
+        mAccountType = value;
         return this;
     }
 
-    public StreamItemEntryBuilder setActionUri(String actionUri) {
-        mActionUri = actionUri;
+    public StreamItemEntryBuilder setAccountName(String value) {
+        mAccountName = value;
+        return this;
+    }
+
+    public StreamItemEntryBuilder setDataSet(String value) {
+        mDataSet = value;
         return this;
     }
 
     public StreamItemEntry build() {
-        return new StreamItemEntry(mId, mText, mComment, mTimestamp, mAction, mActionUri,
-                mResPackage, mIconRes, mLabelRes);
+        return new StreamItemEntry(mId, mText, mComment, mTimestamp, mAccountType, mAccountName,
+                mDataSet, mResPackage, mIconRes, mLabelRes);
     }
 }
\ No newline at end of file