Merge "Throttle view notifications"
diff --git a/res/layout-w470dp/contact_detail_fragment.xml b/res/layout-w470dp/contact_detail_fragment.xml
index d63236d..982abbb 100644
--- a/res/layout-w470dp/contact_detail_fragment.xml
+++ b/res/layout-w470dp/contact_detail_fragment.xml
@@ -76,7 +76,6 @@
         android:layout_height="match_parent"
         android:layout_alignParentLeft="true"
         android:layout_alignParentTop="true"
-        android:background="@android:color/black"
         android:visibility="gone"/>
 
     <View
diff --git a/res/layout-w470dp/contact_detail_updates_fragment.xml b/res/layout-w470dp/contact_detail_updates_fragment.xml
index 60d728d..ccb7123 100644
--- a/res/layout-w470dp/contact_detail_updates_fragment.xml
+++ b/res/layout-w470dp/contact_detail_updates_fragment.xml
@@ -32,7 +32,6 @@
         android:layout_height="match_parent"
         android:layout_alignParentLeft="true"
         android:layout_alignParentTop="true"
-        android:background="@android:color/black"
         android:visibility="gone"/>
 
     <View
diff --git a/res/layout/carousel_about_tab.xml b/res/layout/carousel_about_tab.xml
index ad763be..c7c4394 100644
--- a/res/layout/carousel_about_tab.xml
+++ b/res/layout/carousel_about_tab.xml
@@ -19,8 +19,7 @@
     class="com.android.contacts.detail.CarouselTab"
     android:layout_width="0dip"
     android:layout_height="match_parent"
-    android:layout_weight="1"
-    android:background="@color/detail_tab_background">
+    android:layout_weight="1">
 
     <ImageView android:id="@+id/photo"
         android:scaleType="centerCrop"
@@ -45,8 +44,6 @@
         android:layout_alignParentLeft="true"
         android:layout_alignParentTop="true"
         android:layout_marginBottom="@dimen/detail_tab_carousel_tab_label_height"
-        android:background="@android:color/black"
-        android:alpha="0"
         android:visibility="gone"/>
 
     <TextView
diff --git a/res/layout/carousel_updates_tab.xml b/res/layout/carousel_updates_tab.xml
index b41829f..9637023 100644
--- a/res/layout/carousel_updates_tab.xml
+++ b/res/layout/carousel_updates_tab.xml
@@ -33,7 +33,6 @@
         android:layout_alignParentLeft="true"
         android:visibility="gone" />
 
-
     <!-- Transparent view to overlay on the update photo
     (to allow white text to appear over a white photo). -->
     <View
@@ -65,8 +64,6 @@
         android:layout_alignParentLeft="true"
         android:layout_alignParentTop="true"
         android:layout_marginBottom="@dimen/detail_tab_carousel_tab_label_height"
-        android:background="@android:color/black"
-        android:alpha="0"
         android:visibility="gone"/>
 
     <TextView
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/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/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..677ac10 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -270,25 +270,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 4b4d50d..dbfe411 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -1124,7 +1124,7 @@
             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;
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/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..cfca831 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 */
@@ -753,7 +754,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..a40379d 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) {
@@ -1052,45 +248,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 +279,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 +319,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/CallLogNotificationsService.java b/src/com/android/contacts/calllog/CallLogNotificationsService.java
index 6dd24aa..be540ad 100644
--- a/src/com/android/contacts/calllog/CallLogNotificationsService.java
+++ b/src/com/android/contacts/calllog/CallLogNotificationsService.java
@@ -26,8 +26,8 @@
  * <p>
  * It handles the following actions:
  * <ul>
- * <li>{@link #ACTION_MARK_NEW_CALLS_AS_OLD}: marks all the new items in the call log as old; this
- * is called when a notification is dismissed.</li>
+ * <li>{@link #ACTION_MARK_NEW_VOICEMAILS_AS_OLD}: marks all the new voicemails in the call log as
+ * old; this is called when a notification is dismissed.</li>
  * <li>{@link #ACTION_UPDATE_NOTIFICATIONS}: updates the content of the new items notification; it
  * may include an optional extra {@link #EXTRA_NEW_VOICEMAIL_URI}, containing the URI of the new
  * voicemail that has triggered this update (if any).</li>
@@ -36,11 +36,9 @@
 public class CallLogNotificationsService extends IntentService {
     private static final String TAG = "CallLogNotificationsService";
 
-    /**
-     * Action to mark all the new calls as old.
-     */
-    public static final String ACTION_MARK_NEW_CALLS_AS_OLD =
-            "com.android.contacts.calllog.MARK_NEW_CALLS_AS_OLD";
+    /** Action to mark all the new voicemails as old. */
+    public static final String ACTION_MARK_NEW_VOICEMAILS_AS_OLD =
+            "com.android.contacts.calllog.ACTION_MARK_NEW_VOICEMAILS_AS_OLD";
 
     /**
      * Action to update the notifications.
@@ -72,8 +70,8 @@
 
     @Override
     protected void onHandleIntent(Intent intent) {
-        if (ACTION_MARK_NEW_CALLS_AS_OLD.equals(intent.getAction())) {
-            mCallLogQueryHandler.markNewCallsAsOld();
+        if (ACTION_MARK_NEW_VOICEMAILS_AS_OLD.equals(intent.getAction())) {
+            mCallLogQueryHandler.markNewVoicemailsAsOld();
         } else if (ACTION_UPDATE_NOTIFICATIONS.equals(intent.getAction())) {
             Uri voicemailUri = (Uri) intent.getParcelableExtra(EXTRA_NEW_VOICEMAIL_URI);
             DefaultVoicemailNotifier.getInstance(this).updateNotification(voicemailUri);
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 b4e4248..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;
@@ -51,12 +50,13 @@
     private static final int QUERY_OLD_CALLS_TOKEN = 54;
     /** The token for the query to mark all missed calls as old after seeing the call log. */
     private static final int UPDATE_MARK_AS_OLD_TOKEN = 55;
+    /** The token for the query to mark all new voicemails as old. */
+    private static final int UPDATE_MARK_VOICEMAILS_AS_OLD_TOKEN = 56;
     /** The token for the query to mark all missed calls as read after seeing the call log. */
-    private static final int UPDATE_MARK_MISSED_CALL_AS_READ_TOKEN = 56;
+    private static final int UPDATE_MARK_MISSED_CALL_AS_READ_TOKEN = 57;
 
     /** The token for the query to fetch voicemail status messages. */
-    private static final int QUERY_VOICEMAIL_STATUS_TOKEN = 57;
-
+    private static final int QUERY_VOICEMAIL_STATUS_TOKEN = 58;
 
     private final WeakReference<Listener> mListener;
 
@@ -103,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;
     }
 
@@ -192,6 +192,22 @@
                 values, where.toString(), null);
     }
 
+    /** Updates all new voicemails to mark them as old. */
+    public void markNewVoicemailsAsOld() {
+        // Mark all "new" voicemails as not new anymore.
+        StringBuilder where = new StringBuilder();
+        where.append(Calls.NEW);
+        where.append(" = 1 AND ");
+        where.append(Calls.TYPE);
+        where.append(" = ?");
+
+        ContentValues values = new ContentValues(1);
+        values.put(Calls.NEW, "0");
+
+        startUpdate(UPDATE_MARK_VOICEMAILS_AS_OLD_TOKEN, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
+                values, where.toString(), new String[]{ Integer.toString(Calls.VOICEMAIL_TYPE) });
+    }
+
     /** Updates all missed calls to mark them as read. */
     public void markMissedCallsAsRead() {
         // Mark all "new" calls as not new anymore.
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/DefaultVoicemailNotifier.java b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
index 1cf67c1..f795a9c 100644
--- a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
+++ b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
@@ -153,7 +153,7 @@
                 .setContentTitle(title)
                 .setContentText(callers)
                 .setDefaults(callToNotify != null ? Notification.DEFAULT_ALL : 0)
-                .setDeleteIntent(createMarkNewCallsAsOld())
+                .setDeleteIntent(createMarkNewVoicemailsAsOldIntent())
                 .setAutoCancel(true)
                 .getNotification();
 
@@ -182,10 +182,10 @@
         mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
     }
 
-    /** Creates a pending intent that marks all new calls as old. */
-    private PendingIntent createMarkNewCallsAsOld() {
+    /** Creates a pending intent that marks all new voicemails as old. */
+    private PendingIntent createMarkNewVoicemailsAsOldIntent() {
         Intent intent = new Intent(mContext, CallLogNotificationsService.class);
-        intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_CALLS_AS_OLD);
+        intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD);
         return PendingIntent.getService(mContext, 0, intent, 0);
     }
 
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/CarouselTab.java b/src/com/android/contacts/detail/CarouselTab.java
index 9b8efd5..26397ff 100644
--- a/src/com/android/contacts/detail/CarouselTab.java
+++ b/src/com/android/contacts/detail/CarouselTab.java
@@ -94,8 +94,6 @@
 
     @Override
     public void setAlphaLayerValue(float alpha) {
-        if (mAlphaLayer != null) {
-            mAlphaLayer.setAlpha(alpha);
-        }
+        ContactDetailDisplayUtils.setAlphaOnViewBackground(mAlphaLayer, alpha);
     }
 }
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index 131f052..cbdf148 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -452,4 +452,15 @@
             }
         }
     }
+
+    /**
+     * Sets an alpha value on the view.
+     */
+    public static void setAlphaOnViewBackground(View view, float alpha) {
+        if (view != null) {
+            // Convert alpha layer to a black background HEX color with an alpha value for better
+            // performance (i.e. use setBackgroundColor() instead of setAlpha())
+            view.setBackgroundColor((int) (alpha * 255) << 24);
+        }
+    }
 }
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 88ae7da..bbf9d5b 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -315,9 +315,7 @@
 
     @Override
     public void setAlphaLayerValue(float alpha) {
-        if (mAlphaLayer != null) {
-            mAlphaLayer.setAlpha(alpha);
-        }
+        ContactDetailDisplayUtils.setAlphaOnViewBackground(mAlphaLayer, alpha);
     }
 
     @Override
diff --git a/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
index 561d44e..7efcc51 100644
--- a/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
@@ -98,27 +98,16 @@
             mAllowedHorizontalScrollLength = (2 * fragmentWidth) - screenWidth;
             mLowerThreshold = (screenWidth - fragmentWidth) / 2;
             mUpperThreshold = mAllowedHorizontalScrollLength - mLowerThreshold;
-
-            // Snap to the current page now that the allowed horizontal scroll length has been
-            // computed.
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    if (isAttachedToWindow() && mAboutFragment != null &&
-                            mUpdatesFragment != null) {
-                        snapToEdge();
-                    }
-                }
-            });
         }
     }
 
     public void setCurrentPage(int pageIndex) {
         if (mCurrentPage != pageIndex) {
             mCurrentPage = pageIndex;
-            if (isAttachedToWindow() && mAboutFragment != null && mUpdatesFragment != null) {
-                snapToEdge();
-            }
+
+            // This method could have been called before the view has been measured, so snap to edge
+            // only after the view is ready.
+            postRunnableToSnapToEdge();
         }
     }
 
@@ -131,7 +120,26 @@
         mUpdatesFragment.enableAlphaLayer();
         mUpdatesFragment.setAlphaLayerValue(mCurrentPage == UPDATES_PAGE ? 0 : MAX_ALPHA);
 
-        snapToEdge();
+        // This method could have been called before the view has been measured, so snap to edge
+        // only after the view is ready.
+        postRunnableToSnapToEdge();
+    }
+
+    /**
+     * Snap to the currently selected page only once all the view setup and measurement has
+     * completed (i.e. we need to know the allowed horizontal scroll width in order to
+     * snap to the correct page).
+     */
+    private void postRunnableToSnapToEdge() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (isAttachedToWindow() && mAboutFragment != null &&
+                        mUpdatesFragment != null) {
+                    snapToEdge();
+                }
+            }
+        });
     }
 
     public int getCurrentPage() {
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index cc4f0ff..5daca05 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -162,7 +162,7 @@
         mAboutTab.setAlphaLayerValue(mLastScrollPosition * MAX_ALPHA /
                 mAllowedHorizontalScrollLength);
         mUpdatesTab.setAlphaLayerValue(MAX_ALPHA - mLastScrollPosition * MAX_ALPHA /
-                mAllowedVerticalScrollLength);
+                mAllowedHorizontalScrollLength);
     }
 
     @Override
diff --git a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
index 02bd314..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);
         }
     };
 
@@ -142,9 +135,7 @@
 
     @Override
     public void setAlphaLayerValue(float alpha) {
-        if (mAlphaLayer != null) {
-            mAlphaLayer.setAlpha(alpha);
-        }
+        ContactDetailDisplayUtils.setAlphaOnViewBackground(mAlphaLayer, alpha);
     }
 
     @Override
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/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 2725d1f..e1b4e9f 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -693,7 +693,7 @@
 
                     @Override
                     public void onRequest(int request) {
-                        if (request == EditorListener.FIELD_CHANGED) {
+                        if (request == EditorListener.FIELD_CHANGED && !isEditingUserProfile()) {
                             acquireAggregationSuggestions(rawContactEditor);
                         }
                     }
@@ -803,12 +803,11 @@
         // TODO: Find a better way to handle shortcuts, i.e. onKeyDown()?
         menu.findItem(R.id.menu_done).setVisible(false);
 
-        boolean editingUserProfile = mIsUserProfile || mNewLocalProfile;
         // Split only if more than one raw profile and not a user profile
         menu.findItem(R.id.menu_split).setVisible(mState != null && mState.size() > 1 &&
-                !editingUserProfile);
+                !isEditingUserProfile());
         // Cannot join a user profile
-        menu.findItem(R.id.menu_join).setVisible(!editingUserProfile);
+        menu.findItem(R.id.menu_join).setVisible(!isEditingUserProfile());
 
 
         int size = menu.size();
@@ -974,7 +973,7 @@
         setEnabled(false);
 
         Intent intent = ContactSaveService.createSaveContactIntent(getActivity(), mState,
-                SAVE_MODE_EXTRA_KEY, saveMode, mNewLocalProfile || mIsUserProfile,
+                SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
                 getActivity().getClass(), ContactEditorActivity.ACTION_SAVE_COMPLETED);
         getActivity().startService(intent);
         return true;
@@ -1150,6 +1149,10 @@
         return false;
     }
 
+    private boolean isEditingUserProfile() {
+        return mNewLocalProfile || mIsUserProfile;
+    }
+
     public static interface Listener {
         /**
          * Contact was not found, so somehow close this fragment. This is raised after a contact
diff --git a/src/com/android/contacts/format/FormatUtils.java b/src/com/android/contacts/format/FormatUtils.java
index 771656b..cb4dc2d 100644
--- a/src/com/android/contacts/format/FormatUtils.java
+++ b/src/com/android/contacts/format/FormatUtils.java
@@ -15,6 +15,7 @@
  */
 package com.android.contacts.format;
 
+import com.android.contacts.test.NeededForTesting;
 import com.google.common.annotations.VisibleForTesting;
 
 import android.database.CharArrayBuffer;
@@ -45,6 +46,7 @@
      * Finds the earliest point in string1 at which the first part of string2 matches.  For example,
      * overlapPoint("abcd", "cdef") == 2.
      */
+    @NeededForTesting  // App itself doesn't use this right now, but we don't want to remove it.
     public static int overlapPoint(String string1, String string2) {
         if (string1 == null || string2 == null) {
             return -1;
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..0448bd5 100644
--- a/src/com/android/contacts/interactions/PhoneNumberInteraction.java
+++ b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
@@ -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/AccountFilterActivity.java b/src/com/android/contacts/list/AccountFilterActivity.java
index 9d872da..fb0cf9e 100644
--- a/src/com/android/contacts/list/AccountFilterActivity.java
+++ b/src/com/android/contacts/list/AccountFilterActivity.java
@@ -83,17 +83,17 @@
             accountFilters.add(ContactListFilter.createAccountFilter(account.type, account.name,
                     account.dataSet, icon, account.name));
         }
-        int count = accountFilters.size();
+        final int count = accountFilters.size();
 
         if (count >= 1) {
-            // If we only have one account, don't show it as "account", instead show it as "all"
             mFilters.add(ContactListFilter.createFilterWithType(
                     ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS));
+            // If we only have one account, don't show it as "account", instead show it as "all"
             if (count > 1) {
                 mFilters.addAll(accountFilters);
-                mFilters.add(ContactListFilter.createFilterWithType(
-                    ContactListFilter.FILTER_TYPE_CUSTOM));
             }
+            mFilters.add(ContactListFilter.createFilterWithType(
+                    ContactListFilter.FILTER_TYPE_CUSTOM));
         }
 
         mListView.setAdapter(new FilterListAdapter(this));
diff --git a/src/com/android/contacts/list/ContactTileAdapter.java b/src/com/android/contacts/list/ContactTileAdapter.java
index cc0a184..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();
     }
 
@@ -183,17 +188,32 @@
     /**
      * Iterates over the {@link Cursor}
      * Returns position of the first NON Starred Contact
-     * Returns -1 if not {@link DisplayType#STREQUENT} or {@link DisplayType#STREQUENT_PHONE_ONLY}
+     * Returns -1 if {@link DisplayType#STARRED_ONLY} or {@link DisplayType#GROUP_MEMBERS}
+     * Returns 0 if {@link DisplayType#FREQUENT_ONLY}
      */
     private int getDividerPosition(Cursor cursor) {
-        if (cursor == null || cursor.isClosed() || (mDisplayType != DisplayType.STREQUENT
-                && mDisplayType != DisplayType.STREQUENT_PHONE_ONLY)) {
-            return -1;
+        if (cursor == null || cursor.isClosed()) {
+            throw new IllegalStateException("Unable to access cursor");
         }
-        while (cursor.moveToNext()) {
-            if (cursor.getInt(mStarredIndex) == 0) {
-                return cursor.getPosition();
-            }
+
+        switch (mDisplayType) {
+            case STREQUENT:
+            case STREQUENT_PHONE_ONLY:
+                while (cursor.moveToNext()) {
+                    if (cursor.getInt(mStarredIndex) == 0) {
+                        return cursor.getPosition();
+                    }
+                }
+                break;
+            case GROUP_MEMBERS:
+            case STARRED_ONLY:
+                // There is no divider
+                return -1;
+            case FREQUENT_ONLY:
+                // Divider is first
+                return 0;
+            default:
+                throw new IllegalStateException("Unrecognized DisplayType " + mDisplayType);
         }
 
         // There are not NON Starred contacts in cursor
@@ -354,6 +374,7 @@
             // Creating new row if needed
             contactTileRowView = new ContactTileRow(mContext, itemViewType);
         }
+
         contactTileRowView.configureRow(contactList, position == getCount() - 1);
         return contactTileRowView;
     }
@@ -390,20 +411,19 @@
     }
     @Override
     public int getViewTypeCount() {
-        return (mDisplayType == DisplayType.STREQUENT ||
-                mDisplayType == DisplayType.STREQUENT_PHONE_ONLY) ? ViewTypes.COUNT : 1;
+        return ViewTypes.COUNT;
     }
 
-    /**
-     * Returns view type based on {@link DisplayType}.
-     * {@link DisplayType#STARRED_ONLY} and {@link DisplayType#GROUP_MEMBERS}
-     * are {@link ViewTypes#STARRED}.
-     * {@link DisplayType#FREQUENT_ONLY} is {@link ViewTypes#FREQUENT}.
-     * {@link DisplayType#STREQUENT} mixes both {@link ViewTypes}
-     * and also adds in {@link ViewTypes#DIVIDER}.
-     */
     @Override
     public int getItemViewType(int position) {
+        /*
+         * Returns view type based on {@link DisplayType}.
+         * {@link DisplayType#STARRED_ONLY} and {@link DisplayType#GROUP_MEMBERS}
+         * are {@link ViewTypes#STARRED}.
+         * {@link DisplayType#FREQUENT_ONLY} is {@link ViewTypes#FREQUENT}.
+         * {@link DisplayType#STREQUENT} mixes both {@link ViewTypes}
+         * and also adds in {@link ViewTypes#DIVIDER}.
+         */
         switch (mDisplayType) {
             case STREQUENT:
                 if (position < getRowCount(mDividerPosition)) {
@@ -483,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;
+            }
         }
     }
 
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/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 5134a1a..ca07516 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -140,11 +140,12 @@
         if (!isSearchMode() && data != null) {
             int count = data.getCount();
             if (count != 0) {
+                count -= (mUserProfileExists ? 1: 0);
                 String format = getResources().getQuantityText(
                         R.plurals.listTotalAllContacts, count).toString();
                 // Do not count the user profile in the contacts count
                 if (mUserProfileExists) {
-                    getAdapter().setContactsCount(String.format(format, count - 1));
+                    getAdapter().setContactsCount(String.format(format, count));
                 } else {
                     mCounterHeaderView.setText(String.format(format, count));
                 }
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index 89de966..bb49027 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -192,16 +192,16 @@
                                 + "SELECT DISTINCT " + RawContacts.CONTACT_ID
                                 + " FROM raw_contacts"
                                 + " WHERE " + RawContacts.ACCOUNT_TYPE + "=?"
-                                + " AND " + RawContacts.ACCOUNT_NAME + "=?"
-                                + " OR " + Contacts.IS_USER_PROFILE + "=1");
+                                + " AND " + RawContacts.ACCOUNT_NAME + "=?");
                 selectionArgs.add(filter.accountType);
                 selectionArgs.add(filter.accountName);
                 if (filter.dataSet != null) {
-                    selection.append(" AND " + RawContacts.DATA_SET + "=?)");
+                    selection.append(" AND " + RawContacts.DATA_SET + "=?");
                     selectionArgs.add(filter.dataSet);
                 } else {
-                    selection.append(" AND " + RawContacts.DATA_SET + " IS NULL)");
+                    selection.append(" AND " + RawContacts.DATA_SET + " IS NULL");
                 }
+                selection.append(" OR " + Contacts.IS_USER_PROFILE + "=1)");
                 break;
             }
             case ContactListFilter.FILTER_TYPE_GROUP: {
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/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/model/AccountTypeTest.java b/tests/src/com/android/contacts/model/AccountTypeTest.java
index de66694..986d840 100644
--- a/tests/src/com/android/contacts/model/AccountTypeTest.java
+++ b/tests/src/com/android/contacts/model/AccountTypeTest.java
@@ -52,7 +52,7 @@
                 AccountType.getResourceText(c, packageName, externalResID, DEFAULT));
 
         // Load from the contacts package itself.
-        final int internalResId = com.android.contacts.R.string.sharedUserLabel;
+        final int internalResId = com.android.contacts.R.string.launcherDialer;
         assertEquals(c.getString(internalResId),
                 AccountType.getResourceText(c, null, internalResId, DEFAULT));
     }
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