Merge "Add activity-aliases to keep shortcuts from GB and HC working"
diff --git a/res/layout/call_detail.xml b/res/layout/call_detail.xml
index 1fe6faa..6a03493 100644
--- a/res/layout/call_detail.xml
+++ b/res/layout/call_detail.xml
@@ -52,7 +52,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/contact_background"
-        android:background="?attr/call_detail_primary_background_color"
+        android:background="?attr/call_log_primary_background_color"
     />
     <LinearLayout
         android:layout_width="match_parent"
@@ -96,14 +96,14 @@
         android:paddingBottom="10dp"
         android:paddingTop="10dp"
         android:paddingLeft="80dp"
-        android:background="?attr/call_detail_secondary_background_color"
+        android:background="?attr/call_log_secondary_background_color"
     >
         <TextView
             android:id="@+id/time"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_alignParentLeft="true"
-            android:textColor="?attr/call_detail_secondary_text_color"
+            android:textColor="?attr/call_log_secondary_text_color"
         />
         <TextView
             android:id="@+id/duration"
@@ -112,7 +112,7 @@
             android:layout_alignParentLeft="true"
             android:layout_below="@id/time"
             android:layout_alignLeft="@id/time"
-            android:textColor="?attr/call_detail_secondary_text_color"
+            android:textColor="?attr/call_log_secondary_text_color"
         />
         <ImageView
             android:id="@+id/delete"
diff --git a/res/layout/call_log_contact_photo.xml b/res/layout/call_log_contact_photo.xml
index 178c45b..552e9ca 100644
--- a/res/layout/call_log_contact_photo.xml
+++ b/res/layout/call_log_contact_photo.xml
@@ -2,9 +2,9 @@
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
     <QuickContactBadge
         android:id="@+id/contact_photo"
-        android:layout_width="?attr/call_log_contact_photo_size"
-        android:layout_height="?attr/call_log_contact_photo_size"
-        android:layout_margin="?attr/call_log_contact_photo_margin"
+        android:layout_width="?attr/call_log_list_contact_photo_size"
+        android:layout_height="?attr/call_log_list_contact_photo_size"
+        android:layout_margin="?attr/call_log_list_contact_photo_margin"
         android:layout_alignParentLeft="true"
         android:layout_gravity="center_vertical"
     />
diff --git a/res/layout/call_log_list_group_item.xml b/res/layout/call_log_list_group_item.xml
index 352d7ec..57465a6 100644
--- a/res/layout/call_log_list_group_item.xml
+++ b/res/layout/call_log_list_group_item.xml
@@ -21,75 +21,5 @@
 
     <include layout="@layout/call_log_contact_photo"/>
     <include layout="@layout/call_log_action_group"/>
-
-    <TextView android:id="@+id/date"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toLeftOf="@id/divider"
-        android:layout_alignParentBottom="true"
-        android:layout_marginBottom="8dip"
-        android:layout_marginLeft="10dip"
-
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:singleLine="true"
-    />
-
-    <TextView android:id="@+id/label"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentLeft="true"
-        android:layout_alignParentBottom="true"
-        android:layout_marginLeft="86dip"
-        android:layout_marginRight="5dip"
-        android:layout_alignBaseline="@id/date"
-
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textStyle="bold"
-    />
-
-    <TextView android:id="@+id/number"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toRightOf="@id/label"
-        android:layout_toLeftOf="@id/date"
-        android:layout_alignBaseline="@id/label"
-        android:layout_alignWithParentIfMissing="true"
-
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-    />
-
-    <TextView android:id="@+id/groupSize"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentTop="true"
-        android:layout_toLeftOf="@id/divider"
-        android:layout_above="@id/date"
-        android:layout_alignWithParentIfMissing="true"
-        android:layout_marginBottom="-10dip"
-
-        android:textAppearance="?android:attr/textAppearanceLarge"
-        android:singleLine="true"
-        android:gravity="center_vertical"
-    />
-
-    <TextView android:id="@+id/line1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentLeft="true"
-        android:layout_alignParentTop="true"
-        android:layout_toLeftOf="@+id/groupSize"
-        android:layout_above="@id/date"
-        android:layout_alignWithParentIfMissing="true"
-        android:layout_marginLeft="86dip"
-        android:layout_marginBottom="-10dip"
-
-        android:textAppearance="?android:attr/textAppearanceLarge"
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:gravity="center_vertical"
-    />
+    <include layout="@layout/call_log_list_item_layout" />
 </RelativeLayout>
diff --git a/res/layout/call_log_list_item_layout.xml b/res/layout/call_log_list_item_layout.xml
index 2128a79..8ea2bf7 100644
--- a/res/layout/call_log_list_item_layout.xml
+++ b/res/layout/call_log_list_item_layout.xml
@@ -15,69 +15,12 @@
 -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <ImageView android:id="@+id/call_type_icon"
+    <RelativeLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentLeft="true"
-        android:layout_marginLeft="54dip"
-    />
-
-    <TextView android:id="@+id/date"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toLeftOf="@id/divider"
-        android:layout_alignParentBottom="true"
-        android:layout_marginBottom="8dip"
-        android:layout_marginLeft="10dip"
-
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:singleLine="true"
-    />
-
-    <TextView android:id="@+id/label"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentLeft="true"
-        android:layout_alignParentBottom="true"
-        android:layout_marginLeft="86dip"
-        android:layout_marginRight="5dip"
-        android:layout_alignBaseline="@id/date"
-
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textStyle="bold"
-    />
-
-    <TextView android:id="@+id/number"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toRightOf="@id/label"
-        android:layout_toLeftOf="@id/date"
-        android:layout_alignBaseline="@id/label"
-        android:layout_alignWithParentIfMissing="true"
-
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-    />
-
-    <TextView android:id="@+id/line1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentLeft="true"
+        android:layout_toRightOf="@id/contact_photo"
         android:layout_alignParentTop="true"
-        android:layout_toLeftOf="@id/divider"
-        android:layout_above="@id/date"
-        android:layout_alignWithParentIfMissing="true"
-        android:layout_marginLeft="86dip"
-        android:layout_marginBottom="-10dip"
-
-        android:textAppearance="?android:attr/textAppearanceLarge"
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:gravity="center_vertical"
-    />
+    >
+        <include layout="@layout/call_log_phone_call_details"/>
+    </RelativeLayout>
 </merge>
diff --git a/res/layout/call_log_phone_call_details.xml b/res/layout/call_log_phone_call_details.xml
index 5da2773..f22efba 100644
--- a/res/layout/call_log_phone_call_details.xml
+++ b/res/layout/call_log_phone_call_details.xml
@@ -16,22 +16,42 @@
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
     <LinearLayout
-        android:id="@+id/call_types"
+        android:id="@+id/call_type"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="?attr/call_detail_primary_text_color"
         android:layout_alignParentLeft="true"
         android:layout_alignParentBottom="true"
-    />
+    >
+        <LinearLayout
+            android:id="@+id/call_type_icons"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+        />
+        <TextView
+            android:id="@+id/call_type_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?attr/call_log_primary_text_color"
+        />
+        <TextView
+            android:id="@+id/call_type_separator"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="?attr/call_log_date_margin"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?attr/call_log_primary_text_color"
+            android:text="@string/call_log_type_date_separator"
+        />
+    </LinearLayout>
     <TextView
         android:id="@+id/date"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="?attr/call_detail_primary_text_color"
-        android:layout_toRightOf="@id/call_types"
-        android:layout_marginLeft="?attr/call_detail_date_margin"
+        android:textColor="?attr/call_log_primary_text_color"
+        android:layout_toRightOf="@id/call_type"
+        android:layout_marginLeft="?attr/call_log_date_margin"
         android:layout_alignParentBottom="true"
     />
     <TextView
@@ -39,16 +59,16 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="?attr/call_detail_primary_text_color"
+        android:textColor="?attr/call_log_primary_text_color"
         android:layout_alignParentLeft="true"
-        android:layout_above="@id/call_types"
+        android:layout_above="@id/call_type"
     />
     <TextView
         android:id="@+id/name"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textColor="?attr/call_detail_primary_text_color"
+        android:textColor="?attr/call_log_primary_text_color"
         android:layout_alignParentLeft="true"
         android:layout_above="@id/number"
         android:paddingBottom="2dp"
diff --git a/res/layout/dialpad_fragment.xml b/res/layout/dialpad_fragment.xml
index aee2c25..9f8c0cf 100644
--- a/res/layout/dialpad_fragment.xml
+++ b/res/layout/dialpad_fragment.xml
@@ -22,20 +22,35 @@
     android:layout_marginLeft="8dip"
     android:layout_marginRight="8dip" >
 
-    <!-- Text field above the keypad where the digits are displayed.
-         It's type is set to NULL (to disable the IME keyboard) in the
-         java code.
-
-         Background drawable can be controlled programatically.
-    -->
-    <EditText android:id="@+id/digits"
+    <!-- Text field and possibly soft menu button above the keypad where
+         the digits are displayed. -->
+    <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="@dimen/dialpad_digits_height"
         android:layout_marginTop="4dip"
         android:gravity="center"
-        android:textAppearance="@style/DialtactsDigitsTextAppearance"
-        android:background="@drawable/dialpad_background"
-        android:textColor="?android:attr/textColorPrimary" />
+        android:background="@drawable/dialpad_background" >
+
+        <!-- Type of this EditText is set to NULL (to disable the IME keyboard)
+             in the java code.
+
+             Background drawable can be controlled programatically. -->
+        <EditText android:id="@+id/digits"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_alignParentLeft="true"
+            android:layout_toLeftOf="@+id/moreoverflow"
+            android:gravity="center"
+            android:textAppearance="@style/DialtactsDigitsTextAppearance"
+            android:textColor="?android:attr/textColorPrimary" />
+
+        <ImageButton android:id="@+id/overflow_menu"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_alignParentRight="true"
+            android:src="@drawable/ic_menu_overflow" />
+
+    </RelativeLayout>
 
     <!-- Keypad section -->
     <include layout="@layout/dialpad" />
diff --git a/res/menu/call_log_options.xml b/res/menu/call_log_options.xml
index 57d637e..1f8de57 100644
--- a/res/menu/call_log_options.xml
+++ b/res/menu/call_log_options.xml
@@ -19,4 +19,9 @@
         android:icon="@android:drawable/ic_menu_close_clear_cancel"
         android:title="@string/recentCalls_deleteAll"
         android:showAsAction="withText" />
+
+    <item
+        android:id="@+id/menu_call_settings_call_log"
+        android:title="@string/call_settings"
+        android:showAsAction="withText" />
 </menu>
diff --git a/res/menu/dialpad_options.xml b/res/menu/dialpad_options.xml
index 77da9cb..c736e34 100644
--- a/res/menu/dialpad_options.xml
+++ b/res/menu/dialpad_options.xml
@@ -30,4 +30,9 @@
         android:icon="@drawable/ic_menu_wait"
         android:title="@string/add_wait"
         android:showAsAction="withText" />
+
+    <item
+        android:id="@+id/menu_call_settings_dialpad"
+        android:title="@string/call_settings"
+        android:showAsAction="withText" />
 </menu>
diff --git a/res/menu/dialtacts_options.xml b/res/menu/dialtacts_options.xml
index a5ed153..aa3af3f 100644
--- a/res/menu/dialtacts_options.xml
+++ b/res/menu/dialtacts_options.xml
@@ -15,11 +15,6 @@
 -->
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
     <item
-        android:id="@+id/menu_call_settings"
-        android:title="@string/call_settings"
-        android:showAsAction="withText" />
-
-    <item
         android:id="@+id/search_on_action_bar"
         android:icon="@android:drawable/ic_menu_search"
         android:showAsAction="always" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e17648a..9e2e085 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -571,6 +571,9 @@
     <!-- Title for missed call details screen -->
     <string name="type_missed">Missed call</string>
 
+    <!-- Title for voicemail details screen -->
+    <string name="type_voicemail">Voicemail</string>
+
     <!-- Description for incoming calls going to voice mail vs. not -->
     <string name="actionIncomingCall">Incoming calls</string>
 
@@ -1601,4 +1604,7 @@
 
     <!-- Title of the notification of new voicemail. -->
     <string name="notification_voicemail_title">New voicemail</string>
+
+    <!-- The separator between the call type text and the date in the call log [CHAR LIMIT=3] -->
+    <string name="call_log_type_date_separator">/</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 914532c..e4b7a86 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -36,10 +36,16 @@
         <item name="list_item_header_text_color">#ffcccccc</item>
         <item name="list_item_header_text_size">14sp</item>
         <item name="contact_filter_popup_width">320dip</item>
-        <!-- CallLogActivity -->
-        <item name="call_log_contact_photo_size">50dip</item>
-        <item name="call_log_contact_photo_margin">5dip</item>
+        <!-- CallLogList -->
+        <item name="call_log_list_contact_photo_size">50dip</item>
+        <item name="call_log_list_contact_photo_margin">5dip</item>
         <item name="call_log_list_item_height">60dip</item>
+        <!-- CallLog -->
+        <item name="call_log_date_margin">5dip</item>
+        <item name="call_log_primary_text_color">#FFFFFF</item>
+        <item name="call_log_primary_background_color">#000000</item>
+        <item name="call_log_secondary_text_color">#FFFFFF</item>
+        <item name="call_log_secondary_background_color">#333333</item>
     </style>
 
     <style name="CallDetailActivityTheme" parent="android:Theme.Holo">
@@ -51,11 +57,12 @@
         <item name="call_detail_action_bar_height">60dip</item>
         <item name="call_detail_action_icon_size">60dip</item>
         <item name="call_detail_contact_background_overlay_alpha">0.25</item>
-        <item name="call_detail_primary_text_color">#FFFFFF</item>
-        <item name="call_detail_date_margin">5dip</item>
-        <item name="call_detail_primary_background_color">#000000</item>
-        <item name="call_detail_secondary_text_color">#FFFFFF</item>
-        <item name="call_detail_secondary_background_color">#333333</item>
+        <!-- CallLog -->
+        <item name="call_log_date_margin">5dip</item>
+        <item name="call_log_primary_text_color">#FFFFFF</item>
+        <item name="call_log_primary_background_color">#000000</item>
+        <item name="call_log_secondary_text_color">#FFFFFF</item>
+        <item name="call_log_secondary_background_color">#333333</item>
     </style>
 
     <style name="ContactDetailActivityTheme" parent="android:Theme.Holo.Light">
@@ -138,16 +145,19 @@
         <attr name="call_detail_contact_photo_size" format="dimension" />
         <attr name="call_detail_action_icon_size" format="dimension" />
         <attr name="call_detail_action_bar_height" format="dimension" />
-        <attr name="call_detail_primary_text_color" format="color" />
-        <attr name="call_detail_primary_background_color" format="color" />
-        <attr name="call_detail_secondary_text_color" format="color" />
-        <attr name="call_detail_secondary_background_color" format="color" />
-        <attr name="call_detail_date_margin" format="dimension" />
     </declare-styleable>
 
-    <declare-styleable name="CallLogActivity">
-        <attr name="call_log_contact_photo_size" format="dimension" />
-        <attr name="call_log_contact_photo_margin" format="dimension" />
+    <declare-styleable name="CallLog">
+        <attr name="call_log_primary_text_color" format="color" />
+        <attr name="call_log_primary_background_color" format="color" />
+        <attr name="call_log_secondary_text_color" format="color" />
+        <attr name="call_log_secondary_background_color" format="color" />
+        <attr name="call_log_date_margin" format="dimension" />
+    </declare-styleable>
+
+    <declare-styleable name="CallLogList">
+        <attr name="call_log_list_contact_photo_size" format="dimension" />
+        <attr name="call_log_list_contact_photo_margin" format="dimension" />
         <attr name="call_log_list_item_height" format="dimension" />
     </declare-styleable>
 
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 7f01481..d08b76a 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -214,6 +214,7 @@
                             Uri.encode(mNumber));
                     Cursor phonesCursor = resolver.query(
                             phoneUri, PHONES_PROJECTION, null, null, null);
+                    String candidateNumberText = mNumber;
                     try {
                         if (phonesCursor != null && phonesCursor.moveToFirst()) {
                             long personId = phonesCursor.getLong(COLUMN_INDEX_ID);
@@ -221,19 +222,20 @@
                                     Contacts.CONTENT_URI, personId);
                             nameText = phonesCursor.getString(COLUMN_INDEX_NAME);
                             photoId = phonesCursor.getLong(COLUMN_INDEX_PHOTO_ID);
-                            mNumber = PhoneNumberUtils.formatNumber(
+                            candidateNumberText = PhoneNumberUtils.formatNumber(
                                     phonesCursor.getString(COLUMN_INDEX_NUMBER),
                                     phonesCursor.getString(COLUMN_INDEX_NORMALIZED_NUMBER),
                                     countryIso);
                             numberType = phonesCursor.getInt(COLUMN_INDEX_TYPE);
                             numberLabel = phonesCursor.getString(COLUMN_INDEX_LABEL);
                         } else {
-                            mNumber = PhoneNumberUtils.formatNumber(mNumber, countryIso);
+                            candidateNumberText =
+                                    PhoneNumberUtils.formatNumber(mNumber, countryIso);
                         }
                     } finally {
-                      if (phonesCursor != null) phonesCursor.close();
+                        if (phonesCursor != null) phonesCursor.close();
+                        numberText = candidateNumberText;
                     }
-                    numberText = mNumber;
 
                     // Let user view contact details if they exist, otherwise add option
                     // to create new contact from this number.
@@ -301,8 +303,9 @@
                     ViewAdapter adapter = new ViewAdapter(this, actions);
                     setListAdapter(adapter);
                 }
-                mPhoneCallDetailsHelper.setPhoneCallDetails(mPhoneCallDetailsViews, date, callType,
-                        nameText, numberText, numberType, numberLabel);
+                mPhoneCallDetailsHelper.setPhoneCallDetails(mPhoneCallDetailsViews,
+                        new PhoneCallDetails(mNumber, numberText, new int[]{ callType }, date,
+                                nameText, numberType, numberLabel), false);
 
                 loadContactPhotos(photoId);
             } else {
diff --git a/src/com/android/contacts/PhoneCallDetails.java b/src/com/android/contacts/PhoneCallDetails.java
new file mode 100644
index 0000000..7b02a88
--- /dev/null
+++ b/src/com/android/contacts/PhoneCallDetails.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+
+/**
+ * The details of a phone call to be shown in the UI.
+ */
+public class PhoneCallDetails {
+    /** The number of the other party involved in the call. */
+    public final CharSequence number;
+    /** The formatted version of {@link #number}. */
+    public final CharSequence formattedNumber;
+    /**
+     * The type of calls, as defined in the call log table, e.g., {@link Calls#INCOMING_TYPE}.
+     * <p>
+     * There might be multiple types if this represents a set of entries grouped together.
+     */
+    public final int[] callTypes;
+    /** The date of the call, in milliseconds since the epoch. */
+    public final long date;
+    /** The name of the contact, or the empty string. */
+    public final CharSequence name;
+    /** The type of phone, e.g., {@link Phone#TYPE_HOME}, 0 if not available. */
+    public final int numberType;
+    /** The custom label associated with the phone number in the contact, or the empty string. */
+    public final CharSequence numberLabel;
+
+    /** Create the details for a call with a number not associated with a contact. */
+    public PhoneCallDetails(CharSequence number, CharSequence formattedNumber, int[] callTypes,
+            long date) {
+        this(number, formattedNumber, callTypes, date, "", 0, "");
+    }
+
+    /** Create the details for a call with a number associated with a contact. */
+    public PhoneCallDetails(CharSequence number, CharSequence formattedNumber, int[] callTypes,
+            long date, CharSequence name, int numberType, CharSequence numberLabel) {
+        this.number = number;
+        this.formattedNumber = formattedNumber;
+        this.callTypes = callTypes;
+        this.date = date;
+        this.name = name;
+        this.numberType = numberType;
+        this.numberLabel = numberLabel;
+    }
+}
diff --git a/src/com/android/contacts/PhoneCallDetailsHelper.java b/src/com/android/contacts/PhoneCallDetailsHelper.java
index 78ca252..7f73b04 100644
--- a/src/com/android/contacts/PhoneCallDetailsHelper.java
+++ b/src/com/android/contacts/PhoneCallDetailsHelper.java
@@ -47,6 +47,14 @@
     private final Drawable mMissedDrawable;
     /** Icon for voicemails. */
     private final Drawable mVoicemailDrawable;
+    /** Name used to identify incoming calls. */
+    private final String mIncomingName;
+    /** Name used to identify outgoing calls. */
+    private final String mOutgoingName;
+    /** Name used to identify missed calls. */
+    private final String mMissedName;
+    /** Name used to identify voicemail calls. */
+    private final String mVoicemailName;
     /** The injected current time in milliseconds since the epoch. Used only by tests. */
     private Long mCurrentTimeMillisForTest;
 
@@ -67,72 +75,72 @@
         mOutgoingDrawable = outgoingDrawable;
         mMissedDrawable = missedDrawable;
         mVoicemailDrawable = voicemailDrawable;
+        // Cache these values so that we do not need to look them up each time.
+        mIncomingName = mResources.getString(R.string.type_incoming);
+        mOutgoingName = mResources.getString(R.string.type_outgoing);
+        mMissedName = mResources.getString(R.string.type_missed);
+        mVoicemailName = mResources.getString(R.string.type_voicemail);
     }
 
-    /**
-     * Fills the call details views with content.
-     *
-     * @param date the date of the call, in milliseconds since the epoch
-     * @param callType the type of call, as defined in the call log table
-     * @param name the name of the contact, if available
-     * @param number the number of the other party involved in the call
-     * @param numberType the type of phone, e.g., {@link Phone#TYPE_HOME}, 0 if not available
-     * @param numberLabel the custom label associated with the phone number in the contact
-     */
-    public void setPhoneCallDetails(PhoneCallDetailsViews views, long date,
-            int callType, CharSequence name, CharSequence number, int numberType,
-            CharSequence numberLabel) {
-        Drawable callTypeDrawable = null;
-        switch (callType) {
-            case Calls.INCOMING_TYPE:
-                callTypeDrawable = mIncomingDrawable;
-                break;
+    /** Fills the call details views with content. */
+    public void setPhoneCallDetails(PhoneCallDetailsViews views, PhoneCallDetails details,
+            boolean useIcons) {
+        if (useIcons) {
+            views.callTypeIcons.removeAllViews();
+            int count = details.callTypes.length;
+            for (int callType : details.callTypes) {
+                ImageView callTypeImage = new ImageView(mContext);
+                callTypeImage.setImageDrawable(getCallTypeDrawable(callType));
+                views.callTypeIcons.addView(callTypeImage);
+            }
+            views.callTypeIcons.setVisibility(View.VISIBLE);
+            views.callTypeText.setVisibility(View.GONE);
+            views.callTypeSeparator.setVisibility(View.GONE);
+        } else {
+            String callTypeName;
+            // Use the name of the first call type.
+            // TODO: We should update this to handle the text for multiple calls as well.
+            int callType = details.callTypes[0];
+            views.callTypeText.setText(getCallTypeText(callType));
+            views.callTypeIcons.removeAllViews();
 
-            case Calls.OUTGOING_TYPE:
-                callTypeDrawable = mOutgoingDrawable;
-                break;
-
-            case Calls.MISSED_TYPE:
-                callTypeDrawable = mMissedDrawable;
-                break;
-
-            case Calls.VOICEMAIL_TYPE:
-                callTypeDrawable = mVoicemailDrawable;
-                break;
+            views.callTypeText.setVisibility(View.VISIBLE);
+            views.callTypeSeparator.setVisibility(View.VISIBLE);
+            views.callTypeIcons.setVisibility(View.GONE);
         }
+
         CharSequence shortDateText =
-            DateUtils.getRelativeTimeSpanString(date,
+            DateUtils.getRelativeTimeSpanString(details.date,
                     getCurrentTimeMillis(),
                     DateUtils.MINUTE_IN_MILLIS,
                     DateUtils.FORMAT_ABBREV_RELATIVE);
 
         CharSequence numberFormattedLabel = null;
         // Only show a label if the number is shown and it is not a SIP address.
-        if (!TextUtils.isEmpty(number) && !PhoneNumberUtils.isUriNumber(number.toString())) {
-            numberFormattedLabel = Phone.getTypeLabel(mResources, numberType, numberLabel);
+        if (!TextUtils.isEmpty(details.number)
+                && !PhoneNumberUtils.isUriNumber(details.number.toString())) {
+            numberFormattedLabel = Phone.getTypeLabel(mResources, details.numberType,
+                    details.numberLabel);
         }
 
-        CharSequence nameText;
-        CharSequence numberText;
-        if (TextUtils.isEmpty(name)) {
-            nameText = getDisplayNumber(number);
+        final CharSequence nameText;
+        final CharSequence numberText;
+        if (TextUtils.isEmpty(details.name)) {
+            nameText = getDisplayNumber(details.number, details.formattedNumber);
             numberText = "";
         } else {
-            nameText = name;
-            numberText = getDisplayNumber(number);
-            if (callType != 0 && numberFormattedLabel != null) {
+            nameText = details.name;
+            CharSequence displayNumber = getDisplayNumber(details.number, details.formattedNumber);
+            if (numberFormattedLabel != null) {
                 numberText = FormatUtils.applyStyleToSpan(Typeface.BOLD,
-                        numberFormattedLabel + " " + number, 0,
+                        numberFormattedLabel + " " + displayNumber, 0,
                         numberFormattedLabel.length(),
                         Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {
+                numberText = displayNumber;
             }
         }
 
-        ImageView callTypeImage = new ImageView(mContext);
-        callTypeImage.setImageDrawable(callTypeDrawable);
-        views.callTypesLayout.removeAllViews();
-        views.callTypesLayout.addView(callTypeImage);
-
         views.dateView.setText(shortDateText);
         views.dateView.setVisibility(View.VISIBLE);
         views.nameView.setText(nameText);
@@ -147,7 +155,47 @@
         }
     }
 
-    private CharSequence getDisplayNumber(CharSequence number) {
+    /** Returns the text used to represent the given call type. */
+    private String getCallTypeText(int callType) {
+        switch (callType) {
+            case Calls.INCOMING_TYPE:
+                return mIncomingName;
+
+            case Calls.OUTGOING_TYPE:
+                return mOutgoingName;
+
+            case Calls.MISSED_TYPE:
+                return mMissedName;
+
+            case Calls.VOICEMAIL_TYPE:
+                return mVoicemailName;
+
+            default:
+                throw new IllegalArgumentException("invalid call type: " + callType);
+        }
+    }
+
+    /** Returns the drawable of the icon associated with the given call type. */
+    private Drawable getCallTypeDrawable(int callType) {
+        switch (callType) {
+            case Calls.INCOMING_TYPE:
+                return mIncomingDrawable;
+
+            case Calls.OUTGOING_TYPE:
+                return mOutgoingDrawable;
+
+            case Calls.MISSED_TYPE:
+                return mMissedDrawable;
+
+            case Calls.VOICEMAIL_TYPE:
+                return mVoicemailDrawable;
+
+            default:
+                throw new IllegalArgumentException("invalid call type: " + callType);
+        }
+    }
+
+    private CharSequence getDisplayNumber(CharSequence number, CharSequence formattedNumber) {
         if (TextUtils.isEmpty(number)) {
             return "";
         }
@@ -163,7 +211,11 @@
         if (PhoneNumberUtils.extractNetworkPortion(number.toString()).equals(mVoicemailNumber)) {
             return mResources.getString(R.string.voicemail);
         }
-        return number;
+        if (TextUtils.isEmpty(formattedNumber)) {
+            return number;
+        } else {
+            return formattedNumber;
+        }
     }
 
     public void setCurrentTimeForTest(long currentTimeMillis) {
diff --git a/src/com/android/contacts/PhoneCallDetailsViews.java b/src/com/android/contacts/PhoneCallDetailsViews.java
index 483ec65..7453af0 100644
--- a/src/com/android/contacts/PhoneCallDetailsViews.java
+++ b/src/com/android/contacts/PhoneCallDetailsViews.java
@@ -25,14 +25,18 @@
  */
 public final class PhoneCallDetailsViews {
     public final TextView nameView;
-    public final LinearLayout callTypesLayout;
+    public final LinearLayout callTypeIcons;
+    public final TextView callTypeText;
+    public final View callTypeSeparator;
     public final TextView dateView;
     public final TextView numberView;
 
-    private PhoneCallDetailsViews(TextView nameView, LinearLayout callTypesLayout,
-            TextView dateView, TextView numberView) {
+    private PhoneCallDetailsViews(TextView nameView, LinearLayout callTypeIcons,
+            TextView callTypeText, View callTypeSeparator, TextView dateView, TextView numberView) {
         this.nameView = nameView;
-        this.callTypesLayout = callTypesLayout;
+        this.callTypeIcons = callTypeIcons;
+        this.callTypeText = callTypeText;
+        this.callTypeSeparator = callTypeSeparator;
         this.dateView = dateView;
         this.numberView = numberView;
     }
@@ -46,13 +50,17 @@
      */
     public static PhoneCallDetailsViews fromView(View view) {
         return new PhoneCallDetailsViews((TextView) view.findViewById(R.id.name),
-                (LinearLayout) view.findViewById(R.id.call_types),
+                (LinearLayout) view.findViewById(R.id.call_type_icons),
+                (TextView) view.findViewById(R.id.call_type_name),
+                view.findViewById(R.id.call_type_separator),
                 (TextView) view.findViewById(R.id.date),
                 (TextView) view.findViewById(R.id.number));
     }
 
     public static PhoneCallDetailsViews createForTest(TextView nameView,
-            LinearLayout callTypesLayout, TextView dateView, TextView numberView) {
-        return new PhoneCallDetailsViews(nameView, callTypesLayout, dateView, numberView);
+            LinearLayout callTypeIcons, TextView callTypeText, View callTypeSeparator,
+            TextView dateView, TextView numberView) {
+        return new PhoneCallDetailsViews(nameView, callTypeIcons, callTypeText, callTypeSeparator,
+                dateView, numberView);
     }
 }
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 800bfb1..7456967 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -218,7 +218,7 @@
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         // First check if the {@link ContactLoaderFragment} can handle the key
-        if (mLoaderFragment.handleKeyDown(keyCode)) return true;
+        if (mLoaderFragment != null && mLoaderFragment.handleKeyDown(keyCode)) return true;
 
         // Otherwise find the correct fragment to handle the event
         FragmentKeyListener mCurrentFragment;
@@ -232,7 +232,7 @@
             default:
                 throw new IllegalStateException("Invalid current item for ViewPager");
         }
-        if (mCurrentFragment.handleKeyDown(keyCode)) return true;
+        if (mCurrentFragment != null && mCurrentFragment.handleKeyDown(keyCode)) return true;
 
         // In the last case, give the key event to the superclass.
         return super.onKeyDown(keyCode, event);
@@ -323,28 +323,40 @@
             mContentView = (ViewGroup) mInflater.inflate(
                     R.layout.contact_detail_container_with_updates, mRootView, false);
             mRootView.addView(mContentView);
+
+            // Make sure all needed views are retrieved. Note that narrow width screens have a
+            // {@link ViewPager} and {@link ContactDetailTabCarousel}, while wide width screens have
+            // a {@link ContactDetailFragmentCarousel}.
+            mViewPager = (ViewPager) findViewById(R.id.pager);
+            if (mViewPager != null) {
+                mViewPager.removeAllViews();
+                mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
+                mViewPager.setOnPageChangeListener(mOnPageChangeListener);
+            }
+
+            mTabCarousel = (ContactDetailTabCarousel) findViewById(R.id.tab_carousel);
+            if (mTabCarousel != null) {
+                mTabCarousel.setListener(mTabCarouselListener);
+            }
+
+            mFragmentCarousel = (ContactDetailFragmentCarousel)
+                    findViewById(R.id.fragment_carousel);
         }
 
-        // Narrow width screens have a {@link ViewPager} and {@link ContactDetailTabCarousel}
-        mViewPager = (ViewPager) findViewById(R.id.pager);
-        if (mViewPager != null) {
-            mViewPager.removeAllViews();
-            mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
-            mViewPager.setOnPageChangeListener(mOnPageChangeListener);
-        }
-
-        mTabCarousel = (ContactDetailTabCarousel) findViewById(R.id.tab_carousel);
+        // Then reset the contact data to the appropriate views
         if (mTabCarousel != null) {
-            mTabCarousel.setListener(mTabCarouselListener);
             mTabCarousel.loadData(mContactData);
         }
-
-        // Otherwise, wide width screens have a {@link ContactDetailFragmentCarousel}
-        mFragmentCarousel = (ContactDetailFragmentCarousel) findViewById(R.id.fragment_carousel);
         if (mFragmentCarousel != null) {
             if (mDetailFragment != null) mFragmentCarousel.setAboutFragment(mDetailFragment);
             if (mUpdatesFragment != null) mFragmentCarousel.setUpdatesFragment(mUpdatesFragment);
         }
+        if (mDetailFragment != null) {
+            mDetailFragment.setData(mLookupUri, mContactData);
+        }
+        if (mUpdatesFragment != null) {
+            mUpdatesFragment.setData(mLookupUri, mContactData);
+        }
     }
 
     private void setupContactWithoutUpdates() {
@@ -353,6 +365,10 @@
                     R.layout.contact_detail_container_without_updates, mRootView, false);
             mRootView.addView(mContentView);
         }
+        // Reset contact data
+        if (mDetailFragment != null) {
+            mDetailFragment.setData(mLookupUri, mContactData);
+        }
     }
 
     private final ContactDetailFragment.Listener mFragmentListener =
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index 5dbe87b..e345db7 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -592,16 +592,6 @@
 
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
-        final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings);
-        if (!mInSearchUi) {
-            callSettingsMenuItem.setVisible(!mInSearchUi);
-            Intent settingsIntent = new Intent(Intent.ACTION_MAIN);
-            settingsIntent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME);
-            callSettingsMenuItem.setIntent(settingsIntent);
-        } else {
-            callSettingsMenuItem.setVisible(false);
-        }
-
         final MenuItem searchMenuItem = menu.findItem(R.id.search_on_action_bar);
         if (mInSearchUi || getActionBar().getSelectedTab().getPosition() == TAB_INDEX_DIALER) {
             searchMenuItem.setVisible(false);
@@ -753,4 +743,11 @@
             ((ViewPagerVisibilityListener) fragment).onVisibilityChanged(visibility);
         }
     }
+
+    /** Returns an Intent to launch Call Settings screen */
+    public static Intent getCallSettingsIntent() {
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME);
+        return intent;
+    }
 }
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 3488bed..bf0b256 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -55,6 +55,7 @@
 import com.android.contacts.util.AccountSelectionUtil;
 import com.android.contacts.util.AccountsListAdapter;
 import com.android.contacts.util.DialogManager;
+import com.android.contacts.util.PhoneCapabilityTester;
 import com.android.contacts.widget.ContextMenuAdapter;
 
 import android.accounts.Account;
@@ -123,12 +124,6 @@
 
     private boolean mSearchMode;
 
-    /**
-     * Whether we have a right-side contact or group detail pane for displaying info on that
-     * contact or group while browsing. Generally means "this is a tablet".
-     */
-    private boolean mContentPaneDisplayed;
-
     private ContactDetailFragment mContactDetailFragment;
     private ContactDetailUpdatesFragment mContactDetailUpdatesFragment;
     private final ContactDetailFragmentListener mContactDetailFragmentListener =
@@ -228,7 +223,6 @@
         } else if (fragment instanceof ContactDetailFragment) {
             mContactDetailFragment = (ContactDetailFragment) fragment;
             mContactDetailFragment.setListener(mContactDetailFragmentListener);
-            mContentPaneDisplayed = true;
         } else if (fragment instanceof ContactDetailUpdatesFragment) {
             mContactDetailUpdatesFragment = (ContactDetailUpdatesFragment) fragment;
         } else if (fragment instanceof ContactsUnavailableFragment) {
@@ -239,11 +233,9 @@
         } else if (fragment instanceof ContactLoaderFragment) {
             mContactDetailLoaderFragment = (ContactLoaderFragment) fragment;
             mContactDetailLoaderFragment.setListener(mContactDetailLoaderFragmentListener);
-            mContentPaneDisplayed = true;
         } else if (fragment instanceof GroupDetailFragment) {
             mGroupDetailFragment = (GroupDetailFragment) fragment;
             mGroupDetailFragment.setListener(mGroupDetailFragmentListener);
-            mContentPaneDisplayed = true;
         } else if (fragment instanceof StrequentContactListFragment) {
             mFavoritesFragment = (StrequentContactListFragment) fragment;
             mFavoritesFragment.setListener(mFavoritesFragmentListener);
@@ -315,7 +307,7 @@
         }
 
         if (mRequest.getActionCode() == ContactsRequest.ACTION_VIEW_CONTACT
-                && !mContentPaneDisplayed) {
+                && !PhoneCapabilityTester.isUsingTwoPanes(this)) {
             redirect = new Intent(this, ContactDetailActivity.class);
             redirect.setAction(Intent.ACTION_VIEW);
             redirect.setData(mRequest.getContactUri());
@@ -428,7 +420,7 @@
     private void setSelectedTab(TabState tab) {
         mSelectedTab = tab;
 
-        if (mContentPaneDisplayed) {
+        if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
             switch (mSelectedTab) {
                 case FAVORITES:
                     mFavoritesView.setVisibility(View.VISIBLE);
@@ -554,7 +546,7 @@
         switch (action) {
             case START_SEARCH_MODE:
                 // Checking if multi fragments are being displayed
-                if (mContentPaneDisplayed) {
+                if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
                     mFavoritesView.setVisibility(View.GONE);
                     mBrowserView.setVisibility(View.VISIBLE);
                     mDetailsView.setVisibility(View.VISIBLE);
@@ -621,21 +613,23 @@
     private void configureContactListFragment() {
         mAllFragment.setSearchMode(mSearchMode);
 
+        final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this);
         mAllFragment.setVisibleScrollbarEnabled(!mSearchMode);
         mAllFragment.setVerticalScrollbarPosition(
-                mContentPaneDisplayed
+                useTwoPane
                         ? View.SCROLLBAR_POSITION_LEFT
                         : View.SCROLLBAR_POSITION_RIGHT);
-        mAllFragment.setSelectionVisible(mContentPaneDisplayed);
-        mAllFragment.setQuickContactEnabled(!mContentPaneDisplayed);
+        mAllFragment.setSelectionVisible(useTwoPane);
+        mAllFragment.setQuickContactEnabled(!useTwoPane);
     }
 
     private void configureGroupListFragment() {
+        final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this);
         mGroupsFragment.setVerticalScrollbarPosition(
-                mContentPaneDisplayed
+                useTwoPane
                         ? View.SCROLLBAR_POSITION_LEFT
                         : View.SCROLLBAR_POSITION_RIGHT);
-        mGroupsFragment.setSelectionVisible(mContentPaneDisplayed);
+        mGroupsFragment.setSelectionVisible(useTwoPane);
     }
 
     @Override
@@ -690,14 +684,14 @@
 
         @Override
         public void onSelectionChange() {
-            if (mContentPaneDisplayed) {
+            if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
                 setupContactDetailFragment(mAllFragment.getSelectedContactUri());
             }
         }
 
         @Override
         public void onViewContactAction(Uri contactLookupUri) {
-            if (mContentPaneDisplayed) {
+            if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
                 setupContactDetailFragment(contactLookupUri);
             } else {
                 startActivity(new Intent(Intent.ACTION_VIEW, contactLookupUri));
@@ -870,7 +864,7 @@
             implements StrequentContactListFragment.Listener {
         @Override
         public void onContactSelected(Uri contactUri) {
-            if (mContentPaneDisplayed) {
+            if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
                 setupContactDetailFragment(contactUri);
             } else {
                 startActivity(new Intent(Intent.ACTION_VIEW, contactUri));
@@ -882,7 +876,7 @@
 
         @Override
         public void onViewGroupAction(Uri groupUri) {
-            if (mContentPaneDisplayed) {
+            if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
                 setupGroupDetailFragment(groupUri);
             } else {
                 Intent intent = new Intent(PeopleActivity.this, GroupDetailActivity.class);
@@ -1161,7 +1155,7 @@
             }
             case SUBACTIVITY_EDIT_CONTACT:
             case SUBACTIVITY_NEW_CONTACT: {
-                if (resultCode == RESULT_OK && mContentPaneDisplayed) {
+                if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) {
                     mRequest.setActionCode(ContactsRequest.ACTION_VIEW_CONTACT);
                     mAllFragment.reloadDataAndSetSelectedUri(data.getData());
                 }
@@ -1170,7 +1164,7 @@
 
             case SUBACTIVITY_NEW_GROUP:
             case SUBACTIVITY_EDIT_GROUP: {
-                if (resultCode == RESULT_OK && mContentPaneDisplayed) {
+                if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) {
                     mRequest.setActionCode(ContactsRequest.ACTION_GROUP);
                     mGroupsFragment.setSelectedUri(data.getData());
                 }
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 84e6a8a..11f8965 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -20,7 +20,10 @@
 import com.android.contacts.CallDetailActivity;
 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.util.ExpirableCache;
 import com.android.internal.telephony.CallerInfo;
@@ -61,13 +64,12 @@
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.widget.AdapterView;
-import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.QuickContactBadge;
-import android.widget.TextView;
 
 import java.lang.ref.WeakReference;
 import java.util.LinkedList;
@@ -92,9 +94,6 @@
                 Calls.DATE,
                 Calls.DURATION,
                 Calls.TYPE,
-                Calls.CACHED_NAME,
-                Calls.CACHED_NUMBER_TYPE,
-                Calls.CACHED_NUMBER_LABEL,
                 Calls.COUNTRY_ISO};
 
         public static final int ID = 0;
@@ -102,10 +101,7 @@
         public static final int DATE = 2;
         public static final int DURATION = 3;
         public static final int CALL_TYPE = 4;
-        public static final int CALLER_NAME = 5;
-        public static final int CALLER_NUMBERTYPE = 6;
-        public static final int CALLER_NUMBERLABEL = 7;
-        public static final int COUNTRY_ISO = 8;
+        public static final int COUNTRY_ISO = 5;
     }
 
     /** The query to use for the phones table */
@@ -147,7 +143,7 @@
     private String mCurrentCountryIso;
     private boolean mScrollToTop;
 
-    private boolean mShowMenu;
+    private boolean mShowOptionsMenu;
 
     public static final class ContactInfo {
         public long personId;
@@ -265,23 +261,25 @@
             mRequests = new LinkedList<CallerInfoQuery>();
             mPreDrawListener = null;
 
-            Drawable drawableIncoming = getResources().getDrawable(
+            Drawable incomingDrawable = getResources().getDrawable(
                     R.drawable.ic_call_log_list_incoming_call);
-            Drawable drawableOutgoing = getResources().getDrawable(
+            Drawable outgoingDrawable = getResources().getDrawable(
                     R.drawable.ic_call_log_list_outgoing_call);
-            Drawable drawableMissed = getResources().getDrawable(
+            Drawable missedDrawable = getResources().getDrawable(
                     R.drawable.ic_call_log_list_missed_call);
-            Drawable drawableVoicemail = getResources().getDrawable(
+            Drawable voicemailDrawable = getResources().getDrawable(
                     R.drawable.ic_call_log_list_voicemail);
-            Drawable drawableCall = getResources().getDrawable(
+            Drawable callDrawable = getResources().getDrawable(
                     R.drawable.ic_call_log_list_action_call);
-            Drawable drawablePlay = getResources().getDrawable(
+            Drawable playDrawable = getResources().getDrawable(
                     R.drawable.ic_call_log_list_action_play);
 
             mContactPhotoManager = ContactPhotoManager.getInstance(getActivity());
-            mCallLogViewsHelper = new CallLogListItemHelper(getResources(), mVoiceMailNumber,
-                    drawableIncoming, drawableOutgoing, drawableMissed, drawableVoicemail,
-                    drawableCall, drawablePlay);
+            PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(
+                    getActivity(), getResources(), mVoiceMailNumber, incomingDrawable,
+                    outgoingDrawable, missedDrawable, voicemailDrawable);
+            mCallLogViewsHelper = new CallLogListItemHelper(phoneCallDetailsHelper, callDrawable,
+                    playDrawable);
         }
 
         /**
@@ -340,32 +338,6 @@
             mContactInfoCache.expireAll();
         }
 
-        private void updateCallLog(CallerInfoQuery ciq, ContactInfo ci) {
-            // Check if they are different. If not, don't update.
-            if (TextUtils.equals(ciq.name, ci.name)
-                    && TextUtils.equals(ciq.numberLabel, ci.label)
-                    && ciq.numberType == ci.type
-                    && ciq.photoId == ci.photoId
-                    && ciq.lookupKey == ci.lookupKey) {
-                return;
-            }
-            ContentValues values = new ContentValues(3);
-            values.put(Calls.CACHED_NAME, ci.name);
-            values.put(Calls.CACHED_NUMBER_TYPE, ci.type);
-            values.put(Calls.CACHED_NUMBER_LABEL, ci.label);
-
-            try {
-                getActivity().getContentResolver().update(Calls.CONTENT_URI_WITH_VOICEMAIL, values,
-                        Calls.NUMBER + "='" + ciq.number + "'", null);
-            } catch (SQLiteDiskIOException e) {
-                Log.w(TAG, "Exception while updating call info", e);
-            } catch (SQLiteFullException e) {
-                Log.w(TAG, "Exception while updating call info", e);
-            } catch (SQLiteDatabaseCorruptException e) {
-                Log.w(TAG, "Exception while updating call info", e);
-            }
-        }
-
         private void enqueueRequest(String number, boolean immediate, int position,
                 String name, int numberType, String numberLabel, long photoId, String lookupKey) {
             CallerInfoQuery ciq = new CallerInfoQuery();
@@ -503,9 +475,6 @@
                     needNotify = true;
                 }
             }
-            if (info != null) {
-                updateCallLog(ciq, info);
-            }
             return needNotify;
         }
 
@@ -614,7 +583,7 @@
         @VisibleForTesting
         @Override
         public void bindStandAloneView(View view, Context context, Cursor cursor) {
-            bindView(context, view, cursor);
+            bindView(view, cursor, 1);
         }
 
         @VisibleForTesting
@@ -630,7 +599,7 @@
         @VisibleForTesting
         @Override
         public void bindChildView(View view, Context context, Cursor cursor) {
-            bindView(context, view, cursor);
+            bindView(view, cursor, 1);
         }
 
         @VisibleForTesting
@@ -647,42 +616,31 @@
         @Override
         public void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
                 boolean expanded) {
-            final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
-            int groupIndicator = expanded
-                    ? com.android.internal.R.drawable.expander_ic_maximized
-                    : com.android.internal.R.drawable.expander_ic_minimized;
-            views.groupIndicator.setImageResource(groupIndicator);
-            views.groupSize.setText("(" + groupSize + ")");
-            bindView(context, view, cursor);
+            bindView(view, cursor, groupSize);
         }
 
         private void findAndCacheViews(View view) {
-
-            // Get the views to bind to
-            CallLogListItemViews views = new CallLogListItemViews();
-            views.line1View = (TextView) view.findViewById(R.id.line1);
-            views.labelView = (TextView) view.findViewById(R.id.label);
-            views.numberView = (TextView) view.findViewById(R.id.number);
-            views.dateView = (TextView) view.findViewById(R.id.date);
-            views.iconView = (ImageView) view.findViewById(R.id.call_type_icon);
-            views.callView = (ImageView) view.findViewById(R.id.call_icon);
+            // Get the views to bind to.
+            CallLogListItemViews views = CallLogListItemViews.fromView(view);
             if (views.callView != null) {
                 views.callView.setOnClickListener(this);
             }
-            views.groupIndicator = (ImageView) view.findViewById(R.id.groupIndicator);
-            views.groupSize = (TextView) view.findViewById(R.id.groupSize);
-            views.photoView = (QuickContactBadge) view.findViewById(R.id.contact_photo);
             view.setTag(views);
         }
 
-        public void bindView(Context context, View view, Cursor c) {
+        /**
+         * 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();
 
             String number = c.getString(CallLogQuery.NUMBER);
-            String formattedNumber = null;
-            String callerName = c.getString(CallLogQuery.CALLER_NAME);
-            int callerNumberType = c.getInt(CallLogQuery.CALLER_NUMBERTYPE);
-            String callerNumberLabel = c.getString(CallLogQuery.CALLER_NUMBERLABEL);
+            long date = c.getLong(CallLogQuery.DATE);
+            final String formattedNumber;
             String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
             // Store away the number so we can call it directly if you click on the call icon
             if (views.callView != null) {
@@ -697,25 +655,14 @@
                 // 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;
+                // Format the cached call_log phone number
+                formattedNumber = formatPhoneNumber(number, null, countryIso);
                 mContactInfoCache.put(number, info);
                 Log.d(TAG, "Contact info missing: " + number);
                 // Request the contact details immediately since they are currently missing.
-                enqueueRequest(number, true, c.getPosition(),
-                        callerName, callerNumberType, callerNumberLabel, 0L, "");
+                enqueueRequest(number, true, c.getPosition(), "", 0, "", 0L, "");
             } else if (info != ContactInfo.EMPTY) { // Has been queried
-                // Check if any data is different from the data cached in the
-                // calls db. If so, queue the request so that we can update
-                // the calls db.
-                if (!TextUtils.equals(info.name, callerName)
-                        || info.type != callerNumberType
-                        || !TextUtils.equals(info.label, callerNumberLabel)) {
-                    // Something is amiss, so sync up.
-                    Log.w(TAG, "Contact info inconsistent: " + number);
-                    // Request the contact details immediately since they are probably wrong.
-                    enqueueRequest(number, true, c.getPosition(),
-                            callerName, callerNumberType, callerNumberLabel, info.photoId,
-                            info.lookupKey);
-                } else if (cachedInfo.isExpired()) {
+                if (cachedInfo.isExpired()) {
                     Log.d(TAG, "Contact info expired: " + number);
                     // Put it back in the cache, therefore marking it as not expired, so that other
                     // entries with the same number will not re-request it.
@@ -732,6 +679,9 @@
                             formatPhoneNumber(info.number, info.normalizedNumber, countryIso);
                 }
                 formattedNumber = info.formattedNumber;
+            } else {
+                // Format the cached call_log phone number
+                formattedNumber = formatPhoneNumber(number, null, countryIso);
             }
 
             long contactId = info.personId;
@@ -740,36 +690,21 @@
             String label = info.label;
             long photoId = info.photoId;
             String lookupKey = info.lookupKey;
-            // If there's no name cached in our hashmap, but there's one in the
-            // calls db, use the one in the calls db. Otherwise the name in our
-            // hashmap is more recent, so it has precedence.
-            if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(callerName)) {
-                name = callerName;
-                ntype = callerNumberType;
-                label = callerNumberLabel;
-
-                // Format the cached call_log phone number
-                formattedNumber = formatPhoneNumber(number, null, countryIso);
-            }
-
             // Assumes the call back feature is on most of the
             // time. For private and unknown numbers: hide it.
             if (views.callView != null) {
                 views.callView.setVisibility(View.VISIBLE);
             }
 
-            if (!TextUtils.isEmpty(name)) {
-                mCallLogViewsHelper.setContactNameLabelAndNumber(views, name, number, ntype, label,
-                        formattedNumber);
+            int[] callTypes = getCallTypes(c, count);
+            final PhoneCallDetails details;
+            if (TextUtils.isEmpty(name)) {
+                details = new PhoneCallDetails(number, formattedNumber, callTypes, date);
             } else {
-                // TODO: Do we need to format the number again? Is formattedNumber already storing
-                // this value?
-                mCallLogViewsHelper.setContactNumberOnly(views, number,
-                        formatPhoneNumber(number, null, countryIso));
+                details = new PhoneCallDetails(number, formattedNumber, callTypes, date, name,
+                        ntype, label);
             }
-            mCallLogViewsHelper.setDate(views, c.getLong(CallLogQuery.DATE),
-                    System.currentTimeMillis());
-            mCallLogViewsHelper.setCallType(views, c.getInt(CallLogQuery.CALL_TYPE));
+            mCallLogViewsHelper.setPhoneCallDetails(views, details , true);
             if (views.photoView != null) {
                 bindQuickContact(views.photoView, photoId, contactId, lookupKey);
             }
@@ -783,6 +718,24 @@
             }
         }
 
+        /**
+         * 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 bindQuickContact(QuickContactBadge view, long photoId, long contactId,
                 String lookupKey) {
             view.assignContactUri(getContactUri(contactId, lookupKey));
@@ -802,6 +755,10 @@
         public void disableRequestProcessingForTest() {
             mRequestProcessingDisabled = true;
         }
+
+        public void injectContactInfoForTest(String number, ContactInfo contactInfo) {
+            mContactInfoCache.put(number, contactInfo);
+        }
     }
 
     private static final class QueryHandler extends AsyncQueryHandler {
@@ -981,7 +938,14 @@
 
     @Override
     public void onPrepareOptionsMenu(Menu menu) {
-        menu.findItem(R.id.delete_all).setVisible(mShowMenu);
+        menu.findItem(R.id.delete_all).setVisible(mShowOptionsMenu);
+        final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings_call_log);
+        if (mShowOptionsMenu) {
+            callSettingsMenuItem.setVisible(true);
+            callSettingsMenuItem.setIntent(DialtactsActivity.getCallSettingsIntent());
+        } else {
+            callSettingsMenuItem.setVisible(false);
+        }
     }
 
     @Override
@@ -1216,6 +1180,6 @@
 
     @Override
     public void onVisibilityChanged(boolean visible) {
-        mShowMenu = visible;
+        mShowOptionsMenu = visible;
     }
 }
diff --git a/src/com/android/contacts/calllog/CallLogListItemHelper.java b/src/com/android/contacts/calllog/CallLogListItemHelper.java
index 56399c0..e4630e9 100644
--- a/src/com/android/contacts/calllog/CallLogListItemHelper.java
+++ b/src/com/android/contacts/calllog/CallLogListItemHelper.java
@@ -16,220 +16,64 @@
 
 package com.android.contacts.calllog;
 
-import com.android.contacts.R;
+import com.android.contacts.PhoneCallDetails;
+import com.android.contacts.PhoneCallDetailsHelper;
 import com.android.internal.telephony.CallerInfo;
 
-import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.view.View;
-import android.view.ViewGroup;
 
 /**
  * Helper class to fill in the views of a call log entry.
  */
 /*package*/ class CallLogListItemHelper {
-    /** The resources used to look up strings. */
-    private final Resources mResources;
-    /** The voicemail number. */
-    private final String mVoiceMailNumber;
-    /** Icon for incoming calls. */
-    private final Drawable mDrawableIncoming;
-    /** Icon for outgoing calls. */
-    private final Drawable mDrawableOutgoing;
-    /** Icon for missed calls. */
-    private final Drawable mDrawableMissed;
-    /** Icon for voicemails. */
-    private final Drawable mDrawableVoicemail;
+    /** Helper for populating the details of a phone call. */
+    private final PhoneCallDetailsHelper mPhoneCallDetailsHelper;
     /** Icon for the call action. */
-    private final Drawable mDrawableCall;
+    private final Drawable mCallDrawable;
     /** Icon for the play action. */
-    private final Drawable mDrawablePlay;
+    private final Drawable mPlayDrawable;
 
     /**
      * Creates a new helper instance.
      *
-     * @param resources used to look up strings
-     * @param voicemailNumber the voicemail number, used to determine if a call was to voicemail
-     * @param drawableIncoming the icon drawn besides an incoming call entry
-     * @param drawableOutgoing the icon drawn besides an outgoing call entry
-     * @param drawableMissed the icon drawn besides a missed call entry
+     * @param phoneCallDetailsHelper used to set the details of a phone call
+     * @param callDrawable used to render the call button, for calling back a person
+     * @param playDrawable used to render the play button, for playing a voicemail
      */
-    public CallLogListItemHelper(Resources resources, String voicemailNumber,
-            Drawable drawableIncoming, Drawable drawableOutgoing, Drawable drawableMissed,
-            Drawable drawableVoicemail, Drawable drawableCall, Drawable drawablePlay) {
-        mResources = resources;
-        mVoiceMailNumber = voicemailNumber;
-        mDrawableIncoming = drawableIncoming;
-        mDrawableOutgoing = drawableOutgoing;
-        mDrawableMissed = drawableMissed;
-        mDrawableVoicemail = drawableVoicemail;
-        mDrawableCall = drawableCall;
-        mDrawablePlay = drawablePlay;
+    public CallLogListItemHelper(PhoneCallDetailsHelper phoneCallDetailsHelper,
+            Drawable callDrawable, Drawable playDrawable) {
+        mPhoneCallDetailsHelper = phoneCallDetailsHelper;
+        mCallDrawable = callDrawable;
+        mPlayDrawable = playDrawable;
     }
 
     /**
      * Sets the name, label, and number for a contact.
      *
      * @param views the views to populate
-     * @param name the name of the contact
-     * @param number the number of the contact
-     * @param numberType the type of the number as it appears in the contact, e.g.,
-     *        {@link Phone#TYPE_HOME}
-     * @param label the label of the number, only used if numberType is {@link Phone#TYPE_CUSTOM}
-     * @param formattedNumber the formatted version of the number above
+     * @param details the details of a phone call needed to fill in the data
+     * @param useIcons whether to use icons to show the type of the call
      */
-    public void setContactNameLabelAndNumber(CallLogListItemViews views, String name, String number,
-            int numberType, String label, String formattedNumber) {
-        views.line1View.setText(name);
-        views.labelView.setVisibility(View.VISIBLE);
-
-        // "type" and "label" are currently unused for SIP addresses.
-        CharSequence numberLabel = null;
-        if (!PhoneNumberUtils.isUriNumber(number)) {
-            numberLabel = Phone.getTypeLabel(mResources, numberType, label);
-        }
-        views.numberView.setVisibility(View.VISIBLE);
-        views.numberView.setText(formattedNumber);
-        if (!TextUtils.isEmpty(numberLabel)) {
-            views.labelView.setText(numberLabel);
-            views.labelView.setVisibility(View.VISIBLE);
-
-            // Zero out the numberView's left margin (see below)
-            ViewGroup.MarginLayoutParams numberLP =
-                    (ViewGroup.MarginLayoutParams) views.numberView.getLayoutParams();
-            numberLP.leftMargin = 0;
-            views.numberView.setLayoutParams(numberLP);
-        } else {
-            // There's nothing to display in views.labelView, so hide it.
-            // We can't set it to View.GONE, since it's the anchor for
-            // numberView in the RelativeLayout, so make it INVISIBLE.
-            //   Also, we need to manually *subtract* some left margin from
-            // numberView to compensate for the right margin built in to
-            // labelView (otherwise the number will be indented by a very
-            // slight amount).
-            //   TODO: a cleaner fix would be to contain both the label and
-            // number inside a LinearLayout, and then set labelView *and*
-            // its padding to GONE when there's no label to display.
-            views.labelView.setText(null);
-            views.labelView.setVisibility(View.INVISIBLE);
-
-            ViewGroup.MarginLayoutParams labelLP =
-                    (ViewGroup.MarginLayoutParams) views.labelView.getLayoutParams();
-            ViewGroup.MarginLayoutParams numberLP =
-                    (ViewGroup.MarginLayoutParams) views.numberView.getLayoutParams();
-            // Equivalent to setting android:layout_marginLeft in XML
-            numberLP.leftMargin = -labelLP.rightMargin;
-            views.numberView.setLayoutParams(numberLP);
-        }
-    }
-
-    /**
-     * Sets the number in a call log entry.
-     * <p>
-     * To be used if we do not have a contact with this number.
-     *
-     * @param views the views to populate
-     * @param number the number of the contact
-     * @param formattedNumber the formatted version of the number above
-     */
-    public void setContactNumberOnly(final CallLogListItemViews views, String number,
-            String formattedNumber) {
-        if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
-            number = mResources.getString(R.string.unknown);
-            if (views.callView != null) {
-                views.callView.setVisibility(View.INVISIBLE);
-            }
-        } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
-            number = mResources.getString(R.string.private_num);
-            if (views.callView != null) {
-                views.callView.setVisibility(View.INVISIBLE);
-            }
-        } else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
-            number = mResources.getString(R.string.payphone);
-            if (views.callView != null) {
-                views.callView.setVisibility(View.INVISIBLE);
-            }
-        } else if (PhoneNumberUtils.extractNetworkPortion(number)
-                        .equals(mVoiceMailNumber)) {
-            number = mResources.getString(R.string.voicemail);
-        } else {
-            // Just a phone number, so use the formatted version of the number.
-            number = formattedNumber;
-        }
-
-        views.line1View.setText(number);
-        views.numberView.setVisibility(View.GONE);
-        views.labelView.setVisibility(View.GONE);
-    }
-
-    /**
-     * Sets the date in the views.
-     *
-     * @param views the views to populate
-     * @param date the date of the call log entry
-     * @param now the current time relative to which the date should be formatted
-     */
-    public void setDate(final CallLogListItemViews views, long date, long now) {
-        views.dateView.setText(
-                DateUtils.getRelativeTimeSpanString(
-                        date, now, DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE));
-    }
-
-    /**
-     * Sets the type of the call in the views.
-     *
-     * @param views the views to populate
-     * @param type the type of call log entry, e.g., {@link Calls#INCOMING_TYPE}
-     */
-    public void setCallType(final CallLogListItemViews views, int type) {
-        if (views.iconView != null) {
-            // Set the call type icon.
-            Drawable drawable = null;
-            switch (type) {
-                case Calls.INCOMING_TYPE:
-                    drawable = mDrawableIncoming;
-                    break;
-
-                case Calls.OUTGOING_TYPE:
-                    drawable = mDrawableOutgoing;
-                    break;
-
-                case Calls.MISSED_TYPE:
-                    drawable = mDrawableMissed;
-                    break;
-
-                case Calls.VOICEMAIL_TYPE:
-                    drawable = mDrawableVoicemail;
-                    break;
-
-                default:
-                    throw new IllegalArgumentException("invalid call type: " + type);
-            }
-            views.iconView.setImageDrawable(drawable);
-        }
+    public void setPhoneCallDetails(CallLogListItemViews views, PhoneCallDetails details,
+            boolean useIcons) {
+        mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details, useIcons);
         if (views.callView != null) {
-            // Set the action icon.
-            Drawable drawable = null;
-            switch (type) {
-                case Calls.INCOMING_TYPE:
-                case Calls.OUTGOING_TYPE:
-                case Calls.MISSED_TYPE:
-                    drawable = mDrawableCall;
-                    break;
-
-                case Calls.VOICEMAIL_TYPE:
-                    drawable = mDrawablePlay;
-                    break;
-
-                default:
-                    throw new IllegalArgumentException("invalid call type: " + type);
-            }
-            views.callView.setImageDrawable(drawable);
+            // The type of icon, call or play, is determined by the first call in the group.
+            views.callView.setImageDrawable(
+                    details.callTypes[0] == Calls.VOICEMAIL_TYPE ? mPlayDrawable : mCallDrawable);
+            views.callView.setVisibility(
+                    canPlaceCallsTo(details.number) ? View.VISIBLE : View.INVISIBLE);
         }
     }
+
+    /** Returns true if it is possible to place a call to the given number. */
+    public boolean canPlaceCallsTo(CharSequence number) {
+        return !(TextUtils.isEmpty(number)
+                || number.equals(CallerInfo.UNKNOWN_NUMBER)
+                || number.equals(CallerInfo.PRIVATE_NUMBER)
+                || number.equals(CallerInfo.PAYPHONE_NUMBER));
+    }
 }
diff --git a/src/com/android/contacts/calllog/CallLogListItemViews.java b/src/com/android/contacts/calllog/CallLogListItemViews.java
index 7264c96..0cf15cc 100644
--- a/src/com/android/contacts/calllog/CallLogListItemViews.java
+++ b/src/com/android/contacts/calllog/CallLogListItemViews.java
@@ -16,37 +16,39 @@
 
 package com.android.contacts.calllog;
 
+import com.android.contacts.PhoneCallDetailsViews;
+import com.android.contacts.R;
+
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.QuickContactBadge;
-import android.widget.TextView;
 
 /**
  * Simple value object containing the various views within a call log entry.
  */
 public final class CallLogListItemViews {
-    /** The first line in the call log entry, containing either the name or the number. */
-    public TextView line1View;
-    /** The label associated with the phone number. */
-    public TextView labelView;
-    /**
-     * The number the call was from or to.
-     * <p>
-     * Only filled in if the number is not already in the first line, i.e., {@link #line1View}.
-     */
-    public TextView numberView;
-    /** The date of the call. */
-    public TextView dateView;
-    /** The icon indicating the type of call. */
-    public ImageView iconView;
-    /** The icon used to place a call to the contact. Only present for non-group entries. */
-    public ImageView callView;
-    /** The icon used to expand and collapse an entry. Only present for group entries. */
-    public ImageView groupIndicator;
-    /**
-     * The text view containing the number of items in the group. Only present for group
-     * entries.
-     */
-    public TextView groupSize;
     /** The quick contact badge for the contact. Only present for group and stand alone entries. */
-    public QuickContactBadge photoView;
+    public final QuickContactBadge photoView;
+    /** The main action button on the entry. */
+    public final ImageView callView;
+    /** The details of the phone call. */
+    public final PhoneCallDetailsViews phoneCallDetailsViews;
+
+    private CallLogListItemViews(QuickContactBadge photoView, ImageView callView,
+            PhoneCallDetailsViews phoneCallDetailsViews) {
+        this.photoView = photoView;
+        this.callView = callView;
+        this.phoneCallDetailsViews = phoneCallDetailsViews;
+    }
+
+    public static CallLogListItemViews fromView(View view) {
+        return new CallLogListItemViews((QuickContactBadge) view.findViewById(R.id.contact_photo),
+                (ImageView) view.findViewById(R.id.call_icon),
+                PhoneCallDetailsViews.fromView(view));
+    }
+
+    public static CallLogListItemViews createForTest(QuickContactBadge photoView,
+            ImageView callView, PhoneCallDetailsViews phoneCallDetailsViews) {
+        return new CallLogListItemViews(photoView, callView, phoneCallDetailsViews);
+    }
 }
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index af706b8..be873c6 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -24,7 +24,6 @@
 import com.android.internal.telephony.ITelephony;
 import com.android.phone.CallLogAsync;
 import com.android.phone.HapticFeedback;
-import com.google.i18n.phonenumbers.PhoneNumberUtil;
 
 import android.app.Activity;
 import android.app.Fragment;
@@ -62,12 +61,14 @@
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
 import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.ListView;
+import android.widget.PopupMenu;
 import android.widget.TextView;
 
 /**
@@ -77,6 +78,7 @@
         implements View.OnClickListener,
         View.OnLongClickListener, View.OnKeyListener,
         AdapterView.OnItemClickListener, TextWatcher,
+        PopupMenu.OnMenuItemClickListener,
         ViewPagerVisibilityListener {
     private static final String TAG = DialpadFragment.class.getSimpleName();
 
@@ -111,7 +113,7 @@
     private ListView mDialpadChooser;
     private DialpadChooserAdapter mDialpadChooserAdapter;
 
-    private boolean mShowMenu;
+    private boolean mShowOptionsMenu;
 
     private boolean mHasVoicemail = false;
 
@@ -266,6 +268,16 @@
             mDigits.addTextChangedListener(mTextWatcher);
         }
 
+        // Soft menu button should appear only when there's no hardware menu button.
+        final View overflowMenuButton = fragmentView.findViewById(R.id.overflow_menu);
+        if (overflowMenuButton != null) {
+            if (ViewConfiguration.get(getActivity()).hasPermanentMenuKey()) {
+                overflowMenuButton.setVisibility(View.GONE);
+            } else {
+                overflowMenuButton.setOnClickListener(this);
+            }
+        }
+
         // Check for the presence of the keypad
         View oneButton = fragmentView.findViewById(R.id.one);
         if (oneButton != null) {
@@ -535,35 +547,51 @@
     @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
         super.onCreateOptionsMenu(menu, inflater);
-        inflater.inflate(R.menu.dialpad_options, menu);
+
+        // If the hardware doesn't have a hardware menu key, we'll show soft menu button on the
+        // right side of digits EditText.
+        if (ViewConfiguration.get(getActivity()).hasPermanentMenuKey()) {
+            inflater.inflate(R.menu.dialpad_options, menu);
+        }
     }
 
     @Override
     public void onPrepareOptionsMenu(Menu menu) {
-        if (mDialpadChooser == null || mDigits == null) {
-            // The layout itself isn't ready yet. Let's ignore this call.
-            return;
+        // Hardware menu key should be available and Views should already be ready.
+        if (ViewConfiguration.get(getActivity()).hasPermanentMenuKey() &&
+                mDialpadChooser != null && mDigits != null) {
+            if (mShowOptionsMenu) {
+                setupMenuItems(menu);
+            } else {
+                menu.findItem(R.id.menu_call_settings_dialpad).setVisible(false);
+                menu.findItem(R.id.menu_add_contacts).setVisible(false);
+                menu.findItem(R.id.menu_2s_pause).setVisible(false);
+                menu.findItem(R.id.menu_add_wait).setVisible(false);
+            }
         }
+    }
 
+    private void setupMenuItems(Menu menu) {
+        final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings_dialpad);
         final MenuItem addToContactMenuItem = menu.findItem(R.id.menu_add_contacts);
-        final MenuItem m2SecPauseMenuItem = menu.findItem(R.id.menu_2s_pause);
-        final MenuItem mWaitMenuItem = menu.findItem(R.id.menu_add_wait);
+        final MenuItem twoSecPauseMenuItem = menu.findItem(R.id.menu_2s_pause);
+        final MenuItem waitMenuItem = menu.findItem(R.id.menu_add_wait);
+
+        callSettingsMenuItem.setVisible(true);
+        callSettingsMenuItem.setIntent(DialtactsActivity.getCallSettingsIntent());
 
         // We show "add to contacts", "2sec pause", and "add wait" menus only when the user is
         // seeing usual dialpads and has typed at least one digit.
         // We never show a menu if the "choose dialpad" UI is up.
-        if (!mShowMenu || dialpadChooserVisible() || isDigitsEmpty()) {
+        if (dialpadChooserVisible() || isDigitsEmpty()) {
             addToContactMenuItem.setVisible(false);
-            m2SecPauseMenuItem.setVisible(false);
-            mWaitMenuItem.setVisible(false);
+            twoSecPauseMenuItem.setVisible(false);
+            waitMenuItem.setVisible(false);
         } else {
-            CharSequence digits = mDigits.getText();
+            final CharSequence digits = mDigits.getText();
 
             // Put the current digits string into an intent
-            Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
-            intent.putExtra(Insert.PHONE, digits);
-            intent.setType(People.CONTENT_ITEM_TYPE);
-            addToContactMenuItem.setIntent(intent);
+            addToContactMenuItem.setIntent(getAddToContactIntent(digits));
             addToContactMenuItem.setVisible(true);
 
             // Check out whether to show Pause & Wait option menu items
@@ -584,25 +612,32 @@
 
                 if (selectionStart != 0) {
                     // Pause can be visible if cursor is not in the begining
-                    m2SecPauseMenuItem.setVisible(true);
+                    twoSecPauseMenuItem.setVisible(true);
 
                     // For Wait to be visible set of condition to meet
-                    mWaitMenuItem.setVisible(showWait(selectionStart,
-                                                      selectionEnd, strDigits));
+                    waitMenuItem.setVisible(showWait(selectionStart, selectionEnd, strDigits));
                 } else {
                     // cursor in the beginning both pause and wait to be invisible
-                    m2SecPauseMenuItem.setVisible(false);
-                    mWaitMenuItem.setVisible(false);
+                    twoSecPauseMenuItem.setVisible(false);
+                    waitMenuItem.setVisible(false);
                 }
             } else {
+                twoSecPauseMenuItem.setVisible(true);
+
                 // cursor is not selected so assume new digit is added to the end
                 int strLength = strDigits.length();
-                mWaitMenuItem.setVisible(showWait(strLength,
-                                                      strLength, strDigits));
+                waitMenuItem.setVisible(showWait(strLength, strLength, strDigits));
             }
         }
     }
 
+    private static Intent getAddToContactIntent(CharSequence digits) {
+        final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+        intent.putExtra(Insert.PHONE, digits);
+        intent.setType(People.CONTENT_ITEM_TYPE);
+        return intent;
+    }
+
     private void keyPressed(int keyCode) {
         mHaptic.vibrate();
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
@@ -706,9 +741,28 @@
                 }
                 return;
             }
+            case R.id.overflow_menu: {
+                PopupMenu popup = constructPopupMenu(view);
+                if (popup != null) {
+                    popup.show();
+                }
+            }
         }
     }
 
+    private PopupMenu constructPopupMenu(View anchorView) {
+        final Context context = getActivity();
+        if (context == null) {
+            return null;
+        }
+        final PopupMenu popupMenu = new PopupMenu(context, anchorView);
+        final Menu menu = popupMenu.getMenu();
+        popupMenu.inflate(R.menu.dialpad_options);
+        popupMenu.setOnMenuItemClickListener(this);
+        setupMenuItems(menu);
+        return popupMenu;
+    }
+
     public boolean onLongClick(View view) {
         final Editable digits = mDigits.getText();
         int id = view.getId();
@@ -1091,6 +1145,11 @@
         return false;
     }
 
+    @Override
+    public boolean onMenuItemClick(MenuItem item) {
+        return onOptionsItemSelected(item);
+    }
+
     /**
      * Updates the dial string (mDigits) after inserting a Pause character (,)
      * or Wait character (;).
@@ -1163,7 +1222,7 @@
      * otherwise returns false. Assumes the passed string is non-empty
      * and the 0th index check is not required.
      */
-    private boolean showWait(int start, int end, String digits) {
+    private static boolean showWait(int start, int end, String digits) {
         if (start == end) {
             // visible false in this case
             if (start > digits.length()) return false;
@@ -1241,6 +1300,6 @@
 
     @Override
     public void onVisibilityChanged(boolean visible) {
-        mShowMenu = visible;
+        mShowOptionsMenu = visible;
     }
 }
diff --git a/src/com/android/contacts/group/GroupBrowseListFragment.java b/src/com/android/contacts/group/GroupBrowseListFragment.java
index 4443a53..0b53acf 100644
--- a/src/com/android/contacts/group/GroupBrowseListFragment.java
+++ b/src/com/android/contacts/group/GroupBrowseListFragment.java
@@ -32,6 +32,7 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Parcelable;
 import android.provider.ContactsContract.Groups;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
@@ -229,6 +230,7 @@
         mAdapter.setSelectionVisible(mSelectionVisible);
         mAdapter.setSelectedGroup(mSelectedGroupUri);
 
+        Parcelable listState = mListView.onSaveInstanceState();
         mListView.setAdapter(mAdapter);
         mListView.setEmptyView(mEmptyView);
         mListView.setOnItemClickListener(new OnItemClickListener() {
@@ -241,6 +243,9 @@
 
         if (mSelectionToScreenRequested) {
             requestSelectionToScreen();
+        } else {
+            // Restore the scroll position.
+            mListView.onRestoreInstanceState(listState);
         }
 
         if (mSelectionVisible && mSelectedGroupUri != null) {
diff --git a/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java b/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
index 5c02959..353c2ec 100644
--- a/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
+++ b/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
@@ -25,6 +25,7 @@
 import android.graphics.drawable.Drawable;
 import android.provider.CallLog.Calls;
 import android.test.AndroidTestCase;
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -41,7 +42,9 @@
     /** The date of the call log entry. */
     private static final long TEST_DATE = 1300000000;
     /** The number of the caller/callee in the log entry. */
-    private static final String TEST_NUMBER = "1-412-555-5555";
+    private static final String TEST_NUMBER = "14125555555";
+    /** The formatted version of {@link #TEST_NUMBER}. */
+    private static final String TEST_FORMATTED_NUMBER = "1-412-255-5555";
     /** A drawable to be used for incoming calls. */
     private static final Drawable TEST_INCOMING_DRAWABLE = new ColorDrawable(Color.BLACK);
     /** A drawable to be used for outgoing calls. */
@@ -64,7 +67,8 @@
                 TEST_VOICEMAIL_NUMBER, TEST_INCOMING_DRAWABLE, TEST_OUTGOING_DRAWABLE,
                 TEST_MISSED_DRAWABLE, TEST_VOICEMAIL_DRAWABLE);
         mViews = PhoneCallDetailsViews.createForTest(new TextView(context),
-                new LinearLayout(context), new TextView(context), new TextView(context));
+                new LinearLayout(context), new TextView(context), new View(context),
+                new TextView(context), new TextView(context));
     }
 
     @Override
@@ -75,27 +79,27 @@
     }
 
     public void testSetPhoneCallDetails_Unknown() {
-        setPhoneCallDetailsWithNumber(CallerInfo.UNKNOWN_NUMBER);
+        setPhoneCallDetailsWithNumber(CallerInfo.UNKNOWN_NUMBER, CallerInfo.UNKNOWN_NUMBER);
         assertNameEqualsResource(R.string.unknown);
     }
 
     public void testSetPhoneCallDetails_Private() {
-        setPhoneCallDetailsWithNumber(CallerInfo.PRIVATE_NUMBER);
+        setPhoneCallDetailsWithNumber(CallerInfo.PRIVATE_NUMBER, CallerInfo.PRIVATE_NUMBER);
         assertNameEqualsResource(R.string.private_num);
     }
 
     public void testSetPhoneCallDetails_Payphone() {
-        setPhoneCallDetailsWithNumber(CallerInfo.PAYPHONE_NUMBER);
+        setPhoneCallDetailsWithNumber(CallerInfo.PAYPHONE_NUMBER, CallerInfo.PAYPHONE_NUMBER);
         assertNameEqualsResource(R.string.payphone);
     }
 
     public void testSetPhoneCallDetails_Voicemail() {
-        setPhoneCallDetailsWithNumber(TEST_VOICEMAIL_NUMBER);
+        setPhoneCallDetailsWithNumber(TEST_VOICEMAIL_NUMBER, TEST_VOICEMAIL_NUMBER);
         assertNameEqualsResource(R.string.voicemail);
     }
 
     public void testSetPhoneCallDetails_Normal() {
-        setPhoneCallDetailsWithNumber("1-412-555-1212");
+        setPhoneCallDetailsWithNumber("14125551212", "1-412-555-1212");
         assertNameEquals("1-412-555-1212");
     }
 
@@ -126,20 +130,40 @@
         }
     }
 
-    public void testSetPhoneCallDetails_CallType() {
-        setPhoneCallDetailsWithCallType(Calls.INCOMING_TYPE);
+    public void testSetPhoneCallDetails_CallTypeIcons() {
+        setPhoneCallDetailsWithCallTypeIcons(Calls.INCOMING_TYPE);
         assertCallTypeIconsEquals(TEST_INCOMING_DRAWABLE);
 
-        setPhoneCallDetailsWithCallType(Calls.OUTGOING_TYPE);
+        setPhoneCallDetailsWithCallTypeIcons(Calls.OUTGOING_TYPE);
         assertCallTypeIconsEquals(TEST_OUTGOING_DRAWABLE);
 
-        setPhoneCallDetailsWithCallType(Calls.MISSED_TYPE);
+        setPhoneCallDetailsWithCallTypeIcons(Calls.MISSED_TYPE);
         assertCallTypeIconsEquals(TEST_MISSED_DRAWABLE);
 
-        setPhoneCallDetailsWithCallType(Calls.VOICEMAIL_TYPE);
+        setPhoneCallDetailsWithCallTypeIcons(Calls.VOICEMAIL_TYPE);
         assertCallTypeIconsEquals(TEST_VOICEMAIL_DRAWABLE);
     }
 
+    public void testSetPhoneCallDetails_CallTypeText() {
+        LocaleTestUtils localeTestUtils = new LocaleTestUtils(getContext());
+        localeTestUtils.setLocale(Locale.US);
+        try {
+            setPhoneCallDetailsWithCallTypeText(Calls.INCOMING_TYPE);
+            assertCallTypeTextEquals("Incoming call");
+
+            setPhoneCallDetailsWithCallTypeText(Calls.OUTGOING_TYPE);
+            assertCallTypeTextEquals("Outgoing call");
+
+            setPhoneCallDetailsWithCallTypeText(Calls.MISSED_TYPE);
+            assertCallTypeTextEquals("Missed call");
+
+            setPhoneCallDetailsWithCallTypeText(Calls.VOICEMAIL_TYPE);
+            assertCallTypeTextEquals("Voicemail");
+        } finally {
+            localeTestUtils.restoreLocale();
+        }
+    }
+
     /** Asserts that the name text field contains the value of the given string resource. */
     private void assertNameEqualsResource(int resId) {
         assertNameEquals(getContext().getString(resId));
@@ -155,28 +179,56 @@
         assertEquals(text, mViews.dateView.getText().toString());
     }
 
-    /** Asserts that the call type linear layout contains the images with the given drawables. */
+    /** Asserts that the call type contains the images with the given drawables. */
     private void assertCallTypeIconsEquals(Drawable... drawables) {
-        assertEquals(drawables.length, mViews.callTypesLayout.getChildCount());
+        assertEquals(drawables.length, mViews.callTypeIcons.getChildCount());
         for (int index = 0; index < drawables.length; ++index) {
             Drawable drawable = drawables[index];
-            ImageView imageView = (ImageView) mViews.callTypesLayout.getChildAt(index);
+            ImageView imageView = (ImageView) mViews.callTypeIcons.getChildAt(index);
             assertEquals(drawable, imageView.getDrawable());
         }
+        assertEquals(View.VISIBLE, mViews.callTypeIcons.getVisibility());
+        assertEquals(View.GONE, mViews.callTypeText.getVisibility());
+        assertEquals(View.GONE, mViews.callTypeSeparator.getVisibility());
+    }
+
+    /** Asserts that the call type contains the given text. */
+    private void assertCallTypeTextEquals(String text) {
+        assertEquals(text, mViews.callTypeText.getText().toString());
+        assertEquals(View.GONE, mViews.callTypeIcons.getVisibility());
+        assertEquals(View.VISIBLE, mViews.callTypeText.getVisibility());
+        assertEquals(View.VISIBLE, mViews.callTypeSeparator.getVisibility());
     }
 
     /** Sets the phone call details with default values and the given number. */
-    private void setPhoneCallDetailsWithNumber(String number) {
-        mHelper.setPhoneCallDetails(mViews, TEST_DATE, Calls.INCOMING_TYPE, "", number, 0, "");
+    private void setPhoneCallDetailsWithNumber(String number, String formattedNumber) {
+        mHelper.setPhoneCallDetails(mViews,
+                new PhoneCallDetails(number, formattedNumber, new int[]{ Calls.INCOMING_TYPE },
+                        TEST_DATE),
+                false);
     }
 
     /** Sets the phone call details with default values and the given date. */
     private void setPhoneCallDetailsWithDate(long date) {
-        mHelper.setPhoneCallDetails(mViews, date, Calls.INCOMING_TYPE, "", TEST_NUMBER, 0, "");
+        mHelper.setPhoneCallDetails(mViews,
+                new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER,
+                        new int[]{ Calls.INCOMING_TYPE }, date),
+                false);
     }
 
-    /** Sets the phone call details with default values and the given call type. */
-    private void setPhoneCallDetailsWithCallType(int callType) {
-        mHelper.setPhoneCallDetails(mViews, TEST_DATE, callType, "", TEST_NUMBER, 0, "");
+    /** Sets the phone call details with default values and the given call types using icons. */
+    private void setPhoneCallDetailsWithCallTypeIcons(int... callTypes) {
+        setPhoneCallDetailsWithCallTypes(true, callTypes);
+    }
+
+    /** Sets the phone call details with default values and the given call types using text. */
+    private void setPhoneCallDetailsWithCallTypeText(int... callTypes) {
+        setPhoneCallDetailsWithCallTypes(false, callTypes);
+    }
+
+    private void setPhoneCallDetailsWithCallTypes(boolean useIcons, int... callTypes) {
+        mHelper.setPhoneCallDetails(mViews,
+                new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, callTypes, TEST_DATE),
+                useIcons);
     }
 }
diff --git a/tests/src/com/android/contacts/activities/CallLogActivityTests.java b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
index 6421a9e..8db291d 100644
--- a/tests/src/com/android/contacts/activities/CallLogActivityTests.java
+++ b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
@@ -18,6 +18,7 @@
 
 import com.android.contacts.R;
 import com.android.contacts.calllog.CallLogFragment;
+import com.android.contacts.calllog.CallLogFragment.ContactInfo;
 import com.android.contacts.calllog.CallLogListItemViews;
 import com.android.internal.telephony.CallerInfo;
 
@@ -27,6 +28,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.provider.CallLog.Calls;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.telephony.PhoneNumberUtils;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -51,25 +53,31 @@
 @LargeTest
 public class CallLogActivityTests
         extends ActivityInstrumentationTestCase2<CallLogActivity> {
-    static private final String TAG = "CallLogActivityTests";
-    static private final String[] CALL_LOG_PROJECTION = new String[] {
+    private static final String TAG = "CallLogActivityTests";
+
+    private static final String[] CALL_LOG_PROJECTION = new String[] {
             Calls._ID,
             Calls.NUMBER,
             Calls.DATE,
             Calls.DURATION,
             Calls.TYPE,
-            Calls.CACHED_NAME,
-            Calls.CACHED_NUMBER_TYPE,
-            Calls.CACHED_NUMBER_LABEL,
             Calls.COUNTRY_ISO,
     };
-    static private final int RAND_DURATION = -1;
-    static private final long NOW = -1L;
+    private static final int RAND_DURATION = -1;
+    private static final long NOW = -1L;
 
+    /** A test value for the person id of a contact. */
+    private static final long TEST_PERSON_ID = 1;
+    /** A test value for the photo id of a contact. */
+    private static final long TEST_PHOTO_ID = 2;
+    /** A test value for the lookup key for contacts. */
+    private static final String TEST_LOOKUP_KEY = "contact_id";
+    /** A test value for the country ISO of the phone number in the call log. */
+    private static final String TEST_COUNTRY_ISO = "US";
     /** A phone number to be used in tests. */
-    private static final String TEST_PHONE_NUMBER = "12125551000";
-    /** The formatted version of {@link #TEST_PHONE_NUMBER}. */
-    private static final String TEST_FORMATTED_PHONE_NUMBER = "1 212-555-1000";
+    private static final String TEST_NUMBER = "12125551000";
+    /** The formatted version of {@link #TEST_NUMBER}. */
+    private static final String TEST_FORMATTED_NUMBER = "1 212-555-1000";
 
     // We get the call list activity and assign is a frame to build
     // its list.  mAdapter is an inner class of
@@ -185,28 +193,26 @@
     @MediumTest
     public void testBindView_NumberOnly() {
         mCursor.moveToFirst();
-        insert(TEST_PHONE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
+        insert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
 
         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
-        assertNameIs(views, TEST_FORMATTED_PHONE_NUMBER);
-        assertNumberLabelIsGone(views);
-        assertNumberIsGone(views);
+        assertNameIs(views, TEST_FORMATTED_NUMBER);
+        assertNumberAndLabelAreGone(views);
     }
 
     @MediumTest
     public void testBindView_WithCachedName() {
         mCursor.moveToFirst();
-        insertWithCachedValues(TEST_PHONE_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
+        insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
                 "John Doe", Phone.TYPE_HOME, "");
         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
 
         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
         assertNameIs(views, "John Doe");
-        assertNumberLabelIsVisible(views);
-        assertNumberIs(views, TEST_FORMATTED_PHONE_NUMBER);
+        assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_HOME));
     }
 
     @MediumTest
@@ -219,51 +225,47 @@
 
         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
         assertNameIs(views, "John Doe");
-        assertNumberLabelIsInvisible(views);
-        assertNumberIs(views, "sip:johndoe@gmail.com");
+        assertNumberAndLabelAre(views, "sip:johndoe@gmail.com", null);
     }
 
     @MediumTest
     public void testBindView_HomeLabel() {
         mCursor.moveToFirst();
-        insertWithCachedValues(TEST_PHONE_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
+        insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
                 "John Doe", Phone.TYPE_HOME, "");
         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
 
         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
         assertNameIs(views, "John Doe");
-        assertNumberLabelIs(views, getTypeLabel(Phone.TYPE_HOME));
-        assertNumberIsVisible(views);
+        assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_HOME));
     }
 
     @MediumTest
     public void testBindView_WorkLabel() {
         mCursor.moveToFirst();
-        insertWithCachedValues(TEST_PHONE_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
+        insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
                 "John Doe", Phone.TYPE_WORK, "");
         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
 
         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
         assertNameIs(views, "John Doe");
-        assertNumberLabelIs(views, getTypeLabel(Phone.TYPE_WORK));
-        assertNumberIsVisible(views);
+        assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_WORK));
     }
 
     @MediumTest
     public void testBindView_CustomLabel() {
         mCursor.moveToFirst();
         String numberLabel = "My label";
-        insertWithCachedValues(TEST_PHONE_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
+        insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
                 "John Doe", Phone.TYPE_CUSTOM, numberLabel);
         View view = mAdapter.newStandAloneView(getActivity(), mParentView);
         mAdapter.bindStandAloneView(view, getActivity(), mCursor);
 
         CallLogListItemViews views = (CallLogListItemViews) view.getTag();
         assertNameIs(views, "John Doe");
-        assertNumberLabelIs(views, numberLabel);
-        assertNumberIsVisible(views);
+        assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, numberLabel);
     }
 
     /** Returns the label associated with a given phone type. */
@@ -275,27 +277,6 @@
     // HELPERS to check conditions on the DB/views
     //
     /**
-     * Check the date of the current list item.
-     * @param date That should be present in the call log list
-     *             item. Only NOW is supported.
-     */
-    private void checkDate(long date) {
-        if (NOW == date) {
-            assertEquals("0 mins ago", mItem.dateView.getText());
-        }
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Checks the right icon is used to represent the call type
-     * (missed, incoming, outgoing.) in the current item.
-     */
-    private void checkCallType(int type) {
-        Bitmap icon = ((BitmapDrawable) mItem.iconView.getDrawable()).getBitmap();
-        assertEquals(mCallTypeIcons.get(type), icon);
-    }
-
-    /**
      * Go over all the views in the list and check that the Call
      * icon's visibility matches the nature of the number.
      */
@@ -407,6 +388,32 @@
      */
     private void insertWithCachedValues(String number, long date, int duration, int type,
             String cachedName, int cachedNumberType, String cachedNumberLabel) {
+        insert(number, date, duration, type);
+        ContactInfo contactInfo = new ContactInfo();
+        contactInfo.personId = TEST_PERSON_ID;
+        contactInfo.name = cachedName;
+        contactInfo.type = cachedNumberType;
+        contactInfo.label = cachedNumberLabel;
+        String formattedNumber = PhoneNumberUtils.formatNumber(number, TEST_COUNTRY_ISO);
+        if (formattedNumber == null) {
+            formattedNumber = number;
+        }
+        contactInfo.formattedNumber = formattedNumber;
+        contactInfo.normalizedNumber = number;
+        contactInfo.photoId = TEST_PHOTO_ID;
+        contactInfo.lookupKey = TEST_LOOKUP_KEY;
+        mAdapter.injectContactInfoForTest(number, contactInfo);
+    }
+
+    /**
+     * Insert a new call entry in the test DB.
+     * @param number The phone number. For unknown and private numbers,
+     *               use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
+     * @param date In millisec since epoch. Use NOW to use the current time.
+     * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
+     * @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 ++;
@@ -424,22 +431,7 @@
             assertEquals(Calls.OUTGOING_TYPE, type);
         }
         row.add(type);  // type
-        row.add(cachedName);  // cached name
-        row.add(cachedNumberType);  // cached number type
-        row.add(cachedNumberLabel);  // cached number label
-        row.add("US");  // country ISO
-    }
-
-    /**
-     * Insert a new call entry in the test DB.
-     * @param number The phone number. For unknown and private numbers,
-     *               use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
-     * @param date In millisec since epoch. Use NOW to use the current time.
-     * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
-     * @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) {
-        insertWithCachedValues(number, date, duration, type, "", Phone.TYPE_HOME, "");
+        row.add(TEST_COUNTRY_ISO);  // country ISO
     }
 
     /**
@@ -513,44 +505,25 @@
 
     /** Asserts that the name text view is shown and contains the given text. */
     private void assertNameIs(CallLogListItemViews views, String name) {
-        assertEquals(View.VISIBLE, views.line1View.getVisibility());
-        assertEquals(name, views.line1View.getText());
+        assertEquals(View.VISIBLE, views.phoneCallDetailsViews.nameView.getVisibility());
+        assertEquals(name, views.phoneCallDetailsViews.nameView.getText());
     }
 
-    /** Asserts that the number label text view is shown and contains the given text. */
-    private void assertNumberLabelIs(CallLogListItemViews views, CharSequence numberLabel) {
-        assertNumberLabelIsVisible(views);
-        assertEquals(numberLabel, views.labelView.getText());
+    /** Asserts that the number and label text view contains the given text. */
+    private void assertNumberAndLabelAre(CallLogListItemViews views, CharSequence number,
+            CharSequence numberLabel) {
+        assertEquals(View.VISIBLE, views.phoneCallDetailsViews.numberView.getVisibility());
+        final CharSequence expectedText;
+        if (numberLabel == null) {
+            expectedText = number;
+        } else {
+            expectedText = numberLabel + " " + number;
+        }
+        assertEquals(expectedText, views.phoneCallDetailsViews.numberView.getText().toString());
     }
 
-    /** Asserts that the number label text view is shown. */
-    private void assertNumberLabelIsVisible(CallLogListItemViews views) {
-        assertEquals(View.VISIBLE, views.labelView.getVisibility());
-    }
-
-    /** Asserts that the number label text view is invisible. */
-    private void assertNumberLabelIsInvisible(CallLogListItemViews views) {
-        assertEquals(View.INVISIBLE, views.labelView.getVisibility());
-    }
-
-    /** Asserts that the number label text view is gone. */
-    private void assertNumberLabelIsGone(CallLogListItemViews views) {
-        assertEquals(View.GONE, views.labelView.getVisibility());
-    }
-
-    /** Asserts that the number text view is shown and contains the given text. */
-    private void assertNumberIs(CallLogListItemViews views, String number) {
-        assertNumberIsVisible(views);
-        assertEquals(number, views.numberView.getText());
-    }
-
-    /** Asserts that the number text view is shown. */
-    private void assertNumberIsVisible(CallLogListItemViews views) {
-        assertEquals(View.VISIBLE, views.numberView.getVisibility());
-    }
-
-    /** Asserts that the number text view is gone. */
-    private void assertNumberIsGone(CallLogListItemViews views) {
-        assertEquals(View.GONE, views.numberView.getVisibility());
+    /** Asserts that the number and label text view is gone. */
+    private void assertNumberAndLabelAreGone(CallLogListItemViews views) {
+        assertEquals(View.GONE, views.phoneCallDetailsViews.numberView.getVisibility());
     }
 }
diff --git a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
index a33b710..a8714b4 100644
--- a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
+++ b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
@@ -16,8 +16,9 @@
 
 package com.android.contacts.calllog;
 
-import com.android.contacts.R;
-import com.android.contacts.util.LocaleTestUtils;
+import com.android.contacts.PhoneCallDetails;
+import com.android.contacts.PhoneCallDetailsHelper;
+import com.android.contacts.PhoneCallDetailsViews;
 import com.android.internal.telephony.CallerInfo;
 
 import android.content.Context;
@@ -28,15 +29,20 @@
 import android.test.AndroidTestCase;
 import android.view.View;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.QuickContactBadge;
 import android.widget.TextView;
 
-import java.util.GregorianCalendar;
-import java.util.Locale;
-
 /**
  * Unit tests for {@link CallLogListItemHelper}.
  */
 public class CallLogListItemHelperTest extends AndroidTestCase {
+    /** A test phone number for phone calls. */
+    private static final String TEST_NUMBER = "14125555555";
+    /** The formatted version of {@link #TEST_NUMBER}. */
+    private static final String TEST_FORMATTED_NUMBER = "1-412-255-5555";
+    /** A test date value for phone calls. */
+    private static final long TEST_DATE = 1300000000;
     /** A test voicemail number. */
     private static final String TEST_VOICEMAIL_NUMBER = "123";
     /** A drawable to be used for incoming calls. */
@@ -62,17 +68,15 @@
     protected void setUp() throws Exception {
         super.setUp();
         Context context = getContext();
-        mHelper = new CallLogListItemHelper(context.getResources(), TEST_VOICEMAIL_NUMBER,
-                TEST_INCOMING_DRAWABLE, TEST_OUTGOING_DRAWABLE, TEST_MISSED_DRAWABLE,
-                TEST_VOICEMAIL_DRAWABLE, TEST_CALL_DRAWABLE, TEST_PLAY_DRAWABLE);
-        mViews = new CallLogListItemViews();
-        // Only set the views that are needed in the tests.
-        mViews.iconView = new ImageView(context);
-        mViews.dateView = new TextView(context);
-        mViews.callView = new ImageView(context);
-        mViews.line1View = new TextView(context);
-        mViews.labelView = new TextView(context);
-        mViews.numberView = new TextView(context);
+        PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(context,
+                context.getResources(), TEST_VOICEMAIL_NUMBER, TEST_INCOMING_DRAWABLE,
+                TEST_OUTGOING_DRAWABLE, TEST_MISSED_DRAWABLE, TEST_VOICEMAIL_DRAWABLE);
+        mHelper = new CallLogListItemHelper(phoneCallDetailsHelper, TEST_CALL_DRAWABLE,
+                TEST_PLAY_DRAWABLE);
+        mViews = CallLogListItemViews.createForTest(new QuickContactBadge(context),
+                new ImageView(context), PhoneCallDetailsViews.createForTest(new TextView(context),
+                        new LinearLayout(context), new TextView(context), new TextView(context),
+                        new TextView(context), new TextView(context)));
     }
 
     @Override
@@ -82,70 +86,50 @@
         super.tearDown();
     }
 
-    public void testSetContactNumberOnly() {
-        mHelper.setContactNumberOnly(mViews, "12125551234", "1-212-555-1234");
-        assertEquals("1-212-555-1234", mViews.line1View.getText());
-        assertEquals(View.GONE, mViews.labelView.getVisibility());
-        assertEquals(View.GONE, mViews.numberView.getVisibility());
+    public void testSetPhoneCallDetails() {
+        setPhoneCallDetailsWithNumber("12125551234", "1-212-555-1234");
         assertEquals(View.VISIBLE, mViews.callView.getVisibility());
+        assertEquals(TEST_CALL_DRAWABLE, mViews.callView.getDrawable());
     }
 
-    public void testSetContactNumberOnly_Unknown() {
-        mHelper.setContactNumberOnly(mViews, CallerInfo.UNKNOWN_NUMBER, "");
-        assertEquals(getContext().getString(R.string.unknown), mViews.line1View.getText());
-        assertEquals(View.GONE, mViews.labelView.getVisibility());
-        assertEquals(View.GONE, mViews.numberView.getVisibility());
+    public void testSetPhoneCallDetails_Unknown() {
+        setPhoneCallDetailsWithNumber(CallerInfo.UNKNOWN_NUMBER, CallerInfo.UNKNOWN_NUMBER);
         assertEquals(View.INVISIBLE, mViews.callView.getVisibility());
     }
 
-    public void testSetContactNumberOnly_Private() {
-        mHelper.setContactNumberOnly(mViews, CallerInfo.PRIVATE_NUMBER, "");
-        assertEquals(getContext().getString(R.string.private_num), mViews.line1View.getText());
-        assertEquals(View.GONE, mViews.labelView.getVisibility());
-        assertEquals(View.GONE, mViews.numberView.getVisibility());
+    public void testSetPhoneCallDetails_Private() {
+        setPhoneCallDetailsWithNumber(CallerInfo.PRIVATE_NUMBER, CallerInfo.PRIVATE_NUMBER);
         assertEquals(View.INVISIBLE, mViews.callView.getVisibility());
     }
 
-    public void testSetContactNumberOnly_Payphone() {
-        mHelper.setContactNumberOnly(mViews, CallerInfo.PAYPHONE_NUMBER, "");
-        assertEquals(getContext().getString(R.string.payphone), mViews.line1View.getText());
-        assertEquals(View.GONE, mViews.labelView.getVisibility());
-        assertEquals(View.GONE, mViews.numberView.getVisibility());
+    public void testSetPhoneCallDetails_Payphone() {
+        setPhoneCallDetailsWithNumber(CallerInfo.PAYPHONE_NUMBER, CallerInfo.PAYPHONE_NUMBER);
         assertEquals(View.INVISIBLE, mViews.callView.getVisibility());
     }
 
-    public void testSetContactNumberOnly_Voicemail() {
-        mHelper.setContactNumberOnly(mViews, TEST_VOICEMAIL_NUMBER, "");
-        assertEquals(getContext().getString(R.string.voicemail), mViews.line1View.getText());
-        assertEquals(View.GONE, mViews.labelView.getVisibility());
-        assertEquals(View.GONE, mViews.numberView.getVisibility());
+    public void testSetPhoneCallDetails_VoicemailNumber() {
+        setPhoneCallDetailsWithNumber(TEST_VOICEMAIL_NUMBER, TEST_VOICEMAIL_NUMBER);
         assertEquals(View.VISIBLE, mViews.callView.getVisibility());
+        assertEquals(TEST_CALL_DRAWABLE, mViews.callView.getDrawable());
     }
 
-    public void testSetDate() {
-        // This test requires the locale to be set to US.
-        LocaleTestUtils localeTestUtils = new LocaleTestUtils(getContext());
-        localeTestUtils.setLocale(Locale.US);
-        try {
-            mHelper.setDate(mViews,
-                    new GregorianCalendar(2011, 5, 1, 12, 0, 0).getTimeInMillis(),
-                    new GregorianCalendar(2011, 5, 1, 13, 0, 0).getTimeInMillis());
-            assertEquals("1 hour ago", mViews.dateView.getText());
-            mHelper.setDate(mViews,
-                    new GregorianCalendar(2010, 5, 1, 12, 0, 0).getTimeInMillis(),
-                    new GregorianCalendar(2011, 5, 1, 13, 0, 0).getTimeInMillis());
-            assertEquals("June 1, 2010", mViews.dateView.getText());
-        } finally {
-            localeTestUtils.restoreLocale();
-        }
+    public void testSetPhoneCallDetails_Voicemail() {
+        setPhoneCallDetailsWithTypes(Calls.VOICEMAIL_TYPE);
+        assertEquals(View.VISIBLE, mViews.callView.getVisibility());
+        assertEquals(TEST_PLAY_DRAWABLE, mViews.callView.getDrawable());
     }
 
-    public void testSetCallType_Icon() {
-        mHelper.setCallType(mViews, Calls.INCOMING_TYPE);
-        assertEquals(TEST_INCOMING_DRAWABLE, mViews.iconView.getDrawable());
-        mHelper.setCallType(mViews, Calls.OUTGOING_TYPE);
-        assertEquals(TEST_OUTGOING_DRAWABLE, mViews.iconView.getDrawable());
-        mHelper.setCallType(mViews, Calls.MISSED_TYPE);
-        assertEquals(TEST_MISSED_DRAWABLE, mViews.iconView.getDrawable());
+    /** Sets the details of a phone call using the specified phone number. */
+    private void setPhoneCallDetailsWithNumber(String number, String formattedNumber) {
+        mHelper.setPhoneCallDetails(mViews,
+                new PhoneCallDetails(number, formattedNumber, new int[]{ Calls.INCOMING_TYPE },
+                        TEST_DATE),
+                true);
+    }
+
+    /** Sets the details of a phone call using the specified call type. */
+    private void setPhoneCallDetailsWithTypes(int... types) {
+        mHelper.setPhoneCallDetails(mViews,
+                new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, types, TEST_DATE), true);
     }
 }