Merge "Provide a simple button to add a contact to the default group "My Contacts""
diff --git a/res/drawable-hdpi/ic_call_incoming_holo_dark.png b/res/drawable-hdpi/ic_call_incoming_holo_dark.png
index df6f190..8351f48 100644
--- a/res/drawable-hdpi/ic_call_incoming_holo_dark.png
+++ b/res/drawable-hdpi/ic_call_incoming_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_call_missed_holo_dark.png b/res/drawable-hdpi/ic_call_missed_holo_dark.png
index a6bdb8d..7c17c30 100644
--- a/res/drawable-hdpi/ic_call_missed_holo_dark.png
+++ b/res/drawable-hdpi/ic_call_missed_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_call_outgoing_holo_dark.png b/res/drawable-hdpi/ic_call_outgoing_holo_dark.png
index 657655d..3b9e0f8 100644
--- a/res/drawable-hdpi/ic_call_outgoing_holo_dark.png
+++ b/res/drawable-hdpi/ic_call_outgoing_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_call_voicemail_holo_dark.png b/res/drawable-hdpi/ic_call_voicemail_holo_dark.png
index 734050d..6d64a36 100644
--- a/res/drawable-hdpi/ic_call_voicemail_holo_dark.png
+++ b/res/drawable-hdpi/ic_call_voicemail_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_incoming_holo_dark.png b/res/drawable-mdpi/ic_call_incoming_holo_dark.png
index 055f9c6..8dcb350 100644
--- a/res/drawable-mdpi/ic_call_incoming_holo_dark.png
+++ b/res/drawable-mdpi/ic_call_incoming_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_missed_holo_dark.png b/res/drawable-mdpi/ic_call_missed_holo_dark.png
index ab098ee..af030cf 100644
--- a/res/drawable-mdpi/ic_call_missed_holo_dark.png
+++ b/res/drawable-mdpi/ic_call_missed_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_outgoing_holo_dark.png b/res/drawable-mdpi/ic_call_outgoing_holo_dark.png
index aaf2b87..38a01b7 100644
--- a/res/drawable-mdpi/ic_call_outgoing_holo_dark.png
+++ b/res/drawable-mdpi/ic_call_outgoing_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_voicemail_holo_dark.png b/res/drawable-mdpi/ic_call_voicemail_holo_dark.png
index bb566bb..bf6d006 100644
--- a/res/drawable-mdpi/ic_call_voicemail_holo_dark.png
+++ b/res/drawable-mdpi/ic_call_voicemail_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_call_incoming_holo_dark.png b/res/drawable-xhdpi/ic_call_incoming_holo_dark.png
index 3671ff0..8eb5f3d 100644
--- a/res/drawable-xhdpi/ic_call_incoming_holo_dark.png
+++ b/res/drawable-xhdpi/ic_call_incoming_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_call_missed_holo_dark.png b/res/drawable-xhdpi/ic_call_missed_holo_dark.png
index 66129f2..6d09a4e 100644
--- a/res/drawable-xhdpi/ic_call_missed_holo_dark.png
+++ b/res/drawable-xhdpi/ic_call_missed_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_call_outgoing_holo_dark.png b/res/drawable-xhdpi/ic_call_outgoing_holo_dark.png
index 4b48928..6360504 100644
--- a/res/drawable-xhdpi/ic_call_outgoing_holo_dark.png
+++ b/res/drawable-xhdpi/ic_call_outgoing_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_call_voicemail_holo_dark.png b/res/drawable-xhdpi/ic_call_voicemail_holo_dark.png
index 97443fb..d9684d1 100644
--- a/res/drawable-xhdpi/ic_call_voicemail_holo_dark.png
+++ b/res/drawable-xhdpi/ic_call_voicemail_holo_dark.png
Binary files differ
diff --git a/res/layout/call_log_list_item.xml b/res/layout/call_log_list_item.xml
index 7e82b40..61b7340 100644
--- a/res/layout/call_log_list_item.xml
+++ b/res/layout/call_log_list_item.xml
@@ -16,11 +16,41 @@
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="?attr/call_log_list_item_height"
+    android:layout_height="wrap_content"
 >
+    <!--
+        This layout may represent either a call log item (but not a group thereof) or one of the
+        headers in the call log.
 
-    <include layout="@layout/call_log_contact_photo"/>
-    <include layout="@layout/call_log_action_call"/>
-    <include layout="@layout/call_log_list_item_layout"/>
+        The former will make the @id/call_log_item visible and the @id/call_log_header gone.
 
+        The latter will make the @id/call_log_header visible and the @id/call_log_item gone
+    -->
+
+    <RelativeLayout
+        android:id="@+id/call_log_item"
+        android:layout_width="fill_parent"
+        android:layout_height="?attr/call_log_list_item_height"
+    >
+        <include layout="@layout/call_log_contact_photo"/>
+        <include layout="@layout/call_log_action_call"/>
+        <include layout="@layout/call_log_list_item_layout"/>
+    </RelativeLayout>
+
+    <LinearLayout
+        android:id="@+id/call_log_header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?attr/call_log_list_header_background"
+        android:layout_marginLeft="5dip"
+        android:layout_marginRight="20dip"
+    >
+        <TextView
+            android:id="@+id/call_log_header_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textColor="?attr/call_log_list_header_text_color"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+        />
+    </LinearLayout>
 </RelativeLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 5641d35..f6591f3 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -57,4 +57,10 @@
 
     <!-- Color of the text in the updates tab in the tab carousel on the contact detail page -->
     <color name="detail_update_tab_text_color">#777777</color>
+
+    <!-- Color of the text describing an unconsumed missed call. -->
+    <color name="call_log_missed_call_highlight_color">#FF0000</color>
+
+    <!-- Color of the text describing an unconsumed voicemail. -->
+    <color name="call_log_voicemail_highlight_color">#0000FF</color>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7006691..806b33f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1613,4 +1613,10 @@
 
     <!-- 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>
+
+    <!-- The header in the call log used to identify missed calls and voicemail that have not yet been consumed [CHAR LIMIT=10] -->
+    <string name="call_log_new_header">New</string>
+
+    <!-- The header in the call log used to identify items that have been already consumed [CHAR LIMIT=10] -->
+    <string name="call_log_old_header">Older</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index af36e21..4638b2e 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -16,6 +16,7 @@
 <resources>
     <style name="DialtactsTheme" parent="android:Theme.Holo.SplitActionBarWhenNarrow">
         <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowBackground">@android:color/black</item>
         <item name="activated_background">@drawable/list_item_activated_background</item>
         <item name="section_header_background">@drawable/list_title_holo</item>
         <item name="list_section_header_height">32dip</item>
@@ -40,6 +41,8 @@
         <item name="call_log_list_contact_photo_size">60dip</item>
         <item name="call_log_list_contact_photo_margin">5dip</item>
         <item name="call_log_list_item_height">70dip</item>
+        <item name="call_log_list_header_text_color">#AAAAFF</item>
+        <item name="call_log_list_header_background">@drawable/call_log_action_bar_bg</item>
         <!-- CallLog -->
         <item name="call_log_date_margin">5dip</item>
         <item name="call_log_primary_text_color">#FFFFFF</item>
@@ -154,6 +157,8 @@
         <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" />
+        <attr name="call_log_list_header_text_color" format="color" />
+        <attr name="call_log_list_header_background" format="reference" />
     </declare-styleable>
 
     <style name="PeopleTheme" parent="android:Theme.Holo.Light.SplitActionBarWhenNarrow">
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 5f55f80..cce48dd 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -224,7 +224,7 @@
 
         // Set the details header, based on the first phone call.
         mPhoneCallDetailsHelper.setPhoneCallDetails(mPhoneCallDetailsViews,
-                details[0], false);
+                details[0], false, false);
 
         // Cache the details about the phone number.
         final Uri numberCallUri = mPhoneNumberHelper.getCallUri(mNumber);
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 194d0ba..775009e 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -371,19 +371,29 @@
     }
 
     /**
-     * Creates an intent that can be sent to this service to create a new group.
+     * Creates an intent that can be sent to this service to create a new group as
+     * well as add new members at the same time.
+     *
+     * @param context of the application
+     * @param account in which the group should be created
+     * @param label is the name of the group (cannot be null)
+     * @param rawContactsToAdd is an array of raw contact IDs for contacts that
+     *            should be added to the group
+     * @param callbackActivity is the activity to send the callback intent to
+     * @param callbackAction is the intent action for the callback intent
      */
-    public static Intent createNewGroupIntent(Context context, Account account, String label,
-            Class<?> callbackActivity, String callbackAction) {
+    public static Intent createNewGroupIntent(Context context, Account account,
+            String label, long[] rawContactsToAdd, Class<?> callbackActivity,
+            String callbackAction) {
         Intent serviceIntent = new Intent(context, ContactSaveService.class);
         serviceIntent.setAction(ContactSaveService.ACTION_CREATE_GROUP);
         serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
         serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
         serviceIntent.putExtra(ContactSaveService.EXTRA_GROUP_LABEL, label);
+        serviceIntent.putExtra(ContactSaveService.EXTRA_RAW_CONTACTS_TO_ADD, rawContactsToAdd);
 
         // Callback intent will be invoked by the service once the new group is
-        // created.  The service will put a group membership row in the extras
-        // of the callback intent.
+        // created.
         Intent callbackIntent = new Intent(context, callbackActivity);
         callbackIntent.setAction(callbackAction);
         serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
@@ -392,28 +402,41 @@
     }
 
     private void createGroup(Intent intent) {
-        String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
-        String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
-        String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
+        final String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
+        final String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
+        final String label = intent.getStringExtra(EXTRA_GROUP_LABEL);
+        final long[] rawContactsToAdd = intent.getLongArrayExtra(EXTRA_RAW_CONTACTS_TO_ADD);
 
         ContentValues values = new ContentValues();
         values.put(Groups.ACCOUNT_TYPE, accountType);
         values.put(Groups.ACCOUNT_NAME, accountName);
         values.put(Groups.TITLE, label);
 
-        Uri groupUri = getContentResolver().insert(Groups.CONTENT_URI, values);
+        final ContentResolver resolver = getContentResolver();
+
+        // Create the new group
+        final Uri groupUri = resolver.insert(Groups.CONTENT_URI, values);
+
+        // If there's no URI, then the insertion failed. Abort early because group members can't be
+        // added if the group doesn't exist
         if (groupUri == null) {
+            Log.e(TAG, "Couldn't create group with label " + label);
             return;
         }
 
+        // Add new group members
+        addMembersToGroup(resolver, rawContactsToAdd, ContentUris.parseId(groupUri));
+
+        // TODO: Move this into the contact editor where it belongs. This needs to be integrated
+        // with the way other intent extras that are passed to the {@link ContactEditorActivity}.
         values.clear();
         values.put(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
         values.put(GroupMembership.GROUP_ROW_ID, ContentUris.parseId(groupUri));
 
         Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
         callbackIntent.setData(groupUri);
+        // TODO: This can be taken out when the above TODO is addressed
         callbackIntent.putExtra(ContactsContract.Intents.Insert.DATA, Lists.newArrayList(values));
-
         deliverCallback(callbackIntent);
     }
 
@@ -527,10 +550,23 @@
         if (label != null) {
             ContentValues values = new ContentValues();
             values.put(Groups.TITLE, label);
-            getContentResolver().update(groupUri, values, null, null);
+            resolver.update(groupUri, values, null, null);
         }
 
-        // Add new group members
+        // Add and remove members if necessary
+        addMembersToGroup(resolver, rawContactsToAdd, groupId);
+        removeMembersFromGroup(resolver, rawContactsToRemove, groupId);
+
+        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
+        callbackIntent.setData(groupUri);
+        deliverCallback(callbackIntent);
+    }
+
+    private void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd,
+            long groupId) {
+        if (rawContactsToAdd == null) {
+            return;
+        }
         for (long rawContactId : rawContactsToAdd) {
             try {
                 final ArrayList<ContentProviderOperation> rawContactOperations =
@@ -577,8 +613,13 @@
                         String.valueOf(groupId), e);
             }
         }
+    }
 
-        // Remove group members
+    private void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove,
+            long groupId) {
+        if (rawContactsToRemove == null) {
+            return;
+        }
         for (long rawContactId : rawContactsToRemove) {
             // Apply the delete operation on the data row for the given raw contact's
             // membership in the given group. If no contact matches the provided selection, then
@@ -588,10 +629,6 @@
                     new String[] { String.valueOf(rawContactId),
                     GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
         }
-
-        Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
-        callbackIntent.setData(groupUri);
-        deliverCallback(callbackIntent);
     }
 
     /**
diff --git a/src/com/android/contacts/PhoneCallDetailsHelper.java b/src/com/android/contacts/PhoneCallDetailsHelper.java
index 6bdfbaa..e115d21 100644
--- a/src/com/android/contacts/PhoneCallDetailsHelper.java
+++ b/src/com/android/contacts/PhoneCallDetailsHelper.java
@@ -60,7 +60,7 @@
 
     /** Fills the call details views with content. */
     public void setPhoneCallDetails(PhoneCallDetailsViews views, PhoneCallDetails details,
-            boolean useIcons) {
+            boolean useIcons, boolean isHighlighted) {
         if (useIcons) {
             views.callTypeIcons.removeAllViews();
             int count = details.callTypes.length;
@@ -77,7 +77,9 @@
             // 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(mCallTypeHelper.getCallTypeText(callType));
+            views.callTypeText.setText(
+                    isHighlighted ? mCallTypeHelper.getHighlightedCallTypeText(callType)
+                            : mCallTypeHelper.getCallTypeText(callType));
             views.callTypeIcons.removeAllViews();
 
             views.callTypeText.setVisibility(View.VISIBLE);
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 7456967..0637fd5 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -174,11 +174,7 @@
     @Override
     public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
             boolean globalSearch) {
-        if (globalSearch) {
-            super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
-        } else {
-            ContactsSearchManager.startSearch(this, initialQuery);
-        }
+        // Ignore search key press
     }
 
     @Override
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index cc0be9d..1ee7f1d 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -122,11 +122,7 @@
     @Override
     public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
             boolean globalSearch) {
-        if (globalSearch) {
-            super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
-        } else {
-            ContactsSearchManager.startSearch(this, initialQuery);
-        }
+        // Ignore search key press
     }
 
     @Override
diff --git a/src/com/android/contacts/activities/GroupDetailActivity.java b/src/com/android/contacts/activities/GroupDetailActivity.java
index 7a74bfd..635c130 100644
--- a/src/com/android/contacts/activities/GroupDetailActivity.java
+++ b/src/com/android/contacts/activities/GroupDetailActivity.java
@@ -90,4 +90,10 @@
         }
         return super.onOptionsItemSelected(item);
     }
+
+    @Override
+    public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
+            boolean globalSearch) {
+        // Ignore search key press
+    }
 }
diff --git a/src/com/android/contacts/activities/GroupEditorActivity.java b/src/com/android/contacts/activities/GroupEditorActivity.java
index fa41051..ecadcec 100644
--- a/src/com/android/contacts/activities/GroupEditorActivity.java
+++ b/src/com/android/contacts/activities/GroupEditorActivity.java
@@ -85,11 +85,7 @@
     @Override
     public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
             boolean globalSearch) {
-        if (globalSearch) {
-            super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
-        } else {
-            ContactsSearchManager.startSearch(this, initialQuery);
-        }
+        // Ignore search key press
     }
 
     @Override
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 90e405a..7e6828e 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.calllog;
 
+import com.android.common.io.MoreCloseables;
 import com.android.common.widget.GroupingListAdapter;
 import com.android.contacts.CallDetailActivity;
 import com.android.contacts.ContactPhotoManager;
@@ -38,6 +39,8 @@
 import android.content.res.Resources;
 import android.database.CharArrayBuffer;
 import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.MergeCursor;
 import android.database.sqlite.SQLiteDatabaseCorruptException;
 import android.database.sqlite.SQLiteDiskIOException;
 import android.database.sqlite.SQLiteException;
@@ -70,6 +73,8 @@
 import java.lang.ref.WeakReference;
 import java.util.LinkedList;
 
+import javax.annotation.concurrent.GuardedBy;
+
 /**
  * Displays a list of call log entries.
  */
@@ -80,21 +85,39 @@
     private static final int CONTACT_INFO_CACHE_SIZE = 100;
 
     /** The query for the call log table */
-    private static final class CallLogQuery {
+    public static final class CallLogQuery {
         public static final String[] _PROJECTION = new String[] {
                 Calls._ID,
                 Calls.NUMBER,
                 Calls.DATE,
                 Calls.DURATION,
                 Calls.TYPE,
-                Calls.COUNTRY_ISO};
-
+                Calls.COUNTRY_ISO,
+        };
         public static final int ID = 0;
         public static final int NUMBER = 1;
         public static final int DATE = 2;
         public static final int DURATION = 3;
         public static final int CALL_TYPE = 4;
         public static final int COUNTRY_ISO = 5;
+
+        /**
+         * The name of the synthetic "section" column.
+         * <p>
+         * This column identifies whether a row is a header or an actual item, and whether it is
+         * part of the new or old calls.
+         */
+        public static final String SECTION_NAME = "section";
+        /** The index of the "section" column in the projection. */
+        public static final int SECTION = 6;
+        /** The value of the "section" column for the header of the new section. */
+        public static final int SECTION_NEW_HEADER = 0;
+        /** The value of the "section" column for the items of the new section. */
+        public static final int SECTION_NEW_ITEM = 1;
+        /** The value of the "section" column for the header of the old section. */
+        public static final int SECTION_OLD_HEADER = 2;
+        /** The value of the "section" column for the items of the old section. */
+        public static final int SECTION_OLD_ITEM = 3;
     }
 
     /** The query to use for the phones table */
@@ -123,11 +146,8 @@
         public static final int DELETE_ALL = 1;
     }
 
-    private static final int QUERY_TOKEN = 53;
-    private static final int UPDATE_TOKEN = 54;
-
     private CallLogAdapter mAdapter;
-    private QueryHandler mQueryHandler;
+    private CallLogQueryHandler mCallLogQueryHandler;
     private String mVoiceMailNumber;
     private String mCurrentCountryIso;
     private boolean mScrollToTop;
@@ -499,7 +519,6 @@
 
         @Override
         protected void addGroups(Cursor cursor) {
-
             int count = cursor.getCount();
             if (count == 0) {
                 return;
@@ -520,7 +539,8 @@
                 // Group adjacent calls with the same number. Make an exception
                 // for the latest item if it was a missed call.  We don't want
                 // a missed call to be hidden inside a group.
-                if (sameNumber && currentCallType != Calls.MISSED_TYPE) {
+                if (sameNumber && currentCallType != Calls.MISSED_TYPE
+                        && !isSectionHeader(cursor)) {
                     groupItemCount++;
                 } else {
                     if (groupItemCount > 1) {
@@ -549,6 +569,18 @@
             }
         }
 
+        private boolean isSectionHeader(Cursor cursor) {
+            int section = cursor.getInt(CallLogQuery.SECTION);
+            return section == CallLogQuery.SECTION_NEW_HEADER
+                    || section == CallLogQuery.SECTION_OLD_HEADER;
+        }
+
+        private boolean isNewSection(Cursor cursor) {
+            int section = cursor.getInt(CallLogQuery.SECTION);
+            return section == CallLogQuery.SECTION_NEW_ITEM
+                    || section == CallLogQuery.SECTION_NEW_HEADER;
+        }
+
         protected boolean equalPhoneNumbers(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
 
             // TODO add PhoneNumberUtils.compare(CharSequence, CharSequence) to avoid
@@ -625,12 +657,32 @@
          */
         private void bindView(View view, Cursor c, int count) {
             final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+            final int section = c.getInt(CallLogQuery.SECTION);
 
-            String number = c.getString(CallLogQuery.NUMBER);
-            long date = c.getLong(CallLogQuery.DATE);
-            long duration = c.getLong(CallLogQuery.DURATION);
+            if (views.standAloneItemView != null) {
+                // This is stand-alone item: it might, however, be a header: check the value of the
+                // section column in the cursor.
+                if (section == CallLogQuery.SECTION_NEW_HEADER
+                        || section == CallLogQuery.SECTION_OLD_HEADER) {
+                    views.standAloneItemView.setVisibility(View.GONE);
+                    views.standAloneHeaderView.setVisibility(View.VISIBLE);
+                    views.standAloneHeaderTextView.setText(
+                            section == CallLogQuery.SECTION_NEW_HEADER
+                                    ? R.string.call_log_new_header
+                                    : R.string.call_log_old_header);
+                    // Nothing else to set up for a header.
+                    return;
+                }
+                // Default case: an item in the call log.
+                views.standAloneItemView.setVisibility(View.VISIBLE);
+                views.standAloneHeaderView.setVisibility(View.GONE);
+            }
+
+            final String number = c.getString(CallLogQuery.NUMBER);
+            final long date = c.getLong(CallLogQuery.DATE);
+            final long duration = c.getLong(CallLogQuery.DURATION);
             final String formattedNumber;
-            String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
+            final 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) {
                 views.callView.setTag(number);
@@ -673,19 +725,19 @@
                 formattedNumber = formatPhoneNumber(number, null, countryIso);
             }
 
-            long personId = info.personId;
-            String name = info.name;
-            int ntype = info.type;
-            String label = info.label;
-            long photoId = info.photoId;
-            String lookupKey = info.lookupKey;
+            final long personId = info.personId;
+            final String name = info.name;
+            final int ntype = info.type;
+            final String label = info.label;
+            final long photoId = info.photoId;
+            final String lookupKey = info.lookupKey;
             // 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);
             }
 
-            int[] callTypes = getCallTypes(c, count);
+            final int[] callTypes = getCallTypes(c, count);
             final PhoneCallDetails details;
             if (TextUtils.isEmpty(name)) {
                 details = new PhoneCallDetails(number, formattedNumber, callTypes, date, duration);
@@ -693,7 +745,13 @@
                 details = new PhoneCallDetails(number, formattedNumber, callTypes, date, duration,
                         name, ntype, label, personId, photoId);
             }
-            mCallLogViewsHelper.setPhoneCallDetails(views, details , true);
+
+            final boolean isNew = isNewSection(c);
+            // Use icons for old items, but text for new ones.
+            final boolean useIcons = !isNew;
+            // New items also use the highlighted version of the text.
+            final boolean isHighlighted = isNew;
+            mCallLogViewsHelper.setPhoneCallDetails(views, details, useIcons, isHighlighted);
             if (views.photoView != null) {
                 bindQuickContact(views.photoView, photoId, personId, lookupKey);
             }
@@ -750,9 +808,22 @@
         }
     }
 
-    private static final class QueryHandler extends AsyncQueryHandler {
+    /** Handles asynchronous queries to the call log. */
+    private static final class CallLogQueryHandler extends AsyncQueryHandler {
+        /** The token for the query to fetch the new entries from the call log. */
+        private static final int QUERY_NEW_CALLS_TOKEN = 53;
+        /** The token for the query to fetch the old entries from the call log. */
+        private static final int QUERY_OLD_CALLS_TOKEN = 54;
+        /** The token for the query to mark all missed calls as old after seeing the call log. */
+        private static final int UPDATE_MISSED_CALLS_TOKEN = 55;
+
         private final WeakReference<CallLogFragment> mFragment;
 
+        /** The cursor containing the new calls, or null if they have not yet been fetched. */
+        @GuardedBy("this") private Cursor mNewCallsCursor;
+        /** The cursor containing the old calls, or null if they have not yet been fetched. */
+        @GuardedBy("this") private Cursor mOldCallsCursor;
+
         /**
          * Simple handler that wraps background calls to catch
          * {@link SQLiteException}, such as when the disk is full.
@@ -783,29 +854,174 @@
             return new CatchingWorkerHandler(looper);
         }
 
-        public QueryHandler(CallLogFragment fragment) {
+        public CallLogQueryHandler(CallLogFragment fragment) {
             super(fragment.getActivity().getContentResolver());
             mFragment = new WeakReference<CallLogFragment>(fragment);
         }
 
+        /** Returns the list of columns for the headers. */
+        private String[] getHeaderColumns() {
+            int length = CallLogQuery._PROJECTION.length;
+            String[] columns = new String[length + 1];
+            System.arraycopy(CallLogQuery._PROJECTION, 0, columns, 0, length);
+            columns[length] = CallLogQuery.SECTION_NAME;
+            return columns;
+        }
+
+        /** Creates a cursor that contains a single row and maps the section to the given value. */
+        private Cursor createHeaderCursorFor(int section) {
+            MatrixCursor matrixCursor = new MatrixCursor(getHeaderColumns());
+            matrixCursor.addRow(new Object[]{ -1L, "", 0L, 0L, 0, "", section });
+            return matrixCursor;
+        }
+
+        /** Returns a cursor for the old calls header. */
+        private Cursor createOldCallsHeaderCursor() {
+            return createHeaderCursorFor(CallLogQuery.SECTION_OLD_HEADER);
+        }
+
+        /** Returns a cursor for the new calls header. */
+        private Cursor createNewCallsHeaderCursor() {
+            return createHeaderCursorFor(CallLogQuery.SECTION_NEW_HEADER);
+        }
+
+        /**
+         * Fetches the list of calls from the call log.
+         * <p>
+         * It will asynchronously update the content of the list view when the fetch completes.
+         */
+        public void fetchCalls() {
+            cancelFetch();
+            invalidate();
+            fetchNewCalls();
+            fetchOldCalls();
+        }
+
+        /** Fetches the list of new calls in the call log. */
+        private void fetchNewCalls() {
+            fetchCalls(QUERY_NEW_CALLS_TOKEN, true);
+        }
+
+        /** Fetch the list of old calls in the call log. */
+        private void fetchOldCalls() {
+            fetchCalls(QUERY_OLD_CALLS_TOKEN, false);
+        }
+
+        /** Fetches the list of calls in the call log, either the new one or the old ones. */
+        private void fetchCalls(int token, boolean isNew) {
+            String selection =
+                    String.format("%s = 1 AND (%s = ? OR %s = ?)",
+                            Calls.NEW, Calls.TYPE, Calls.TYPE);
+            String[] selectionArgs = new String[]{
+                    Integer.toString(Calls.MISSED_TYPE),
+                    Integer.toString(Calls.VOICEMAIL_TYPE),
+            };
+            if (!isNew) {
+                selection = String.format("NOT (%s)", selection);
+            }
+            startQuery(token, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
+                    CallLogQuery._PROJECTION, selection, selectionArgs, Calls.DEFAULT_SORT_ORDER);
+        }
+
+        /** Cancel any pending fetch request. */
+        private void cancelFetch() {
+            cancelOperation(QUERY_NEW_CALLS_TOKEN);
+            cancelOperation(QUERY_OLD_CALLS_TOKEN);
+        }
+
+        /** Updates the missed calls to mark them as old. */
+        public void updateMissedCalls() {
+            // Mark all "new" missed calls as not new anymore
+            StringBuilder where = new StringBuilder();
+            where.append("type = ");
+            where.append(Calls.MISSED_TYPE);
+            where.append(" AND ");
+            where.append(Calls.NEW);
+            where.append(" = 1");
+
+            ContentValues values = new ContentValues(1);
+            values.put(Calls.NEW, "0");
+
+            startUpdate(UPDATE_MISSED_CALLS_TOKEN, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
+                    values, where.toString(), null);
+        }
+
+        /**
+         * Invalidate the current list of calls.
+         * <p>
+         * This method is synchronized because it must close the cursors and reset them atomically.
+         */
+        private synchronized void invalidate() {
+            MoreCloseables.closeQuietly(mNewCallsCursor);
+            MoreCloseables.closeQuietly(mOldCallsCursor);
+            mNewCallsCursor = null;
+            mOldCallsCursor = null;
+        }
+
         @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            final CallLogFragment fragment = mFragment.get();
-            if (fragment != null && fragment.getActivity() != null &&
-                    !fragment.getActivity().isFinishing()) {
-                final CallLogFragment.CallLogAdapter callsAdapter = fragment.mAdapter;
-                callsAdapter.setLoading(false);
-                callsAdapter.changeCursor(cursor);
-                if (fragment.mScrollToTop) {
-                    final ListView listView = fragment.getListView();
-                    if (listView.getFirstVisiblePosition() > 5) {
-                        listView.setSelection(5);
-                    }
-                    listView.smoothScrollToPosition(0);
-                    fragment.mScrollToTop = false;
-                }
+        protected synchronized void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            if (token == QUERY_NEW_CALLS_TOKEN) {
+                // Store the returned cursor.
+                mNewCallsCursor = new ExtendedCursor(
+                        cursor, CallLogQuery.SECTION_NAME, CallLogQuery.SECTION_NEW_ITEM);
+            } else if (token == QUERY_OLD_CALLS_TOKEN) {
+                // Store the returned cursor.
+                mOldCallsCursor = new ExtendedCursor(
+                        cursor, CallLogQuery.SECTION_NAME, CallLogQuery.SECTION_OLD_ITEM);
             } else {
-                cursor.close();
+                Log.w(TAG, "Unknown query completed: ignoring: " + token);
+                return;
+            }
+
+            if (mNewCallsCursor != null && mOldCallsCursor != null) {
+                updateAdapterData(createMergedCursor());
+            }
+        }
+
+        /** Creates the merged cursor representing the data to show in the call log. */
+        @GuardedBy("this")
+        private Cursor createMergedCursor() {
+            try {
+                final boolean noNewCalls = mNewCallsCursor.getCount() == 0;
+                final boolean noOldCalls = mOldCallsCursor.getCount() == 0;
+
+                if (noNewCalls && noOldCalls) {
+                    // Nothing in either cursors.
+                    MoreCloseables.closeQuietly(mNewCallsCursor);
+                    return mOldCallsCursor;
+                }
+
+                if (noNewCalls) {
+                    // Return only the old calls.
+                    MoreCloseables.closeQuietly(mNewCallsCursor);
+                    return new MergeCursor(
+                            new Cursor[]{ createOldCallsHeaderCursor(), mOldCallsCursor });
+                }
+
+                if (noOldCalls) {
+                    // Return only the new calls.
+                    MoreCloseables.closeQuietly(mOldCallsCursor);
+                    return new MergeCursor(
+                            new Cursor[]{ createNewCallsHeaderCursor(), mNewCallsCursor });
+                }
+
+                return new MergeCursor(new Cursor[]{
+                        createNewCallsHeaderCursor(), mNewCallsCursor,
+                        createOldCallsHeaderCursor(), mOldCallsCursor});
+            } finally {
+                // Any cursor still open is now owned, directly or indirectly, by the caller.
+                mNewCallsCursor = null;
+                mOldCallsCursor = null;
+            }
+        }
+
+        /**
+         * Updates the adapter in the call log fragment to show the new cursor data.
+         */
+        private void updateAdapterData(Cursor combinedCursor) {
+            final CallLogFragment fragment = mFragment.get();
+            if (fragment != null) {
+                fragment.onCallsFetched(combinedCursor);
             }
         }
     }
@@ -816,13 +1032,31 @@
 
         mVoiceMailNumber = ((TelephonyManager) getActivity().getSystemService(
                 Context.TELEPHONY_SERVICE)).getVoiceMailNumber();
-        mQueryHandler = new QueryHandler(this);
+        mCallLogQueryHandler = new CallLogQueryHandler(this);
 
         mCurrentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
 
         setHasOptionsMenu(true);
     }
 
+    /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
+    public void onCallsFetched(Cursor cursor) {
+        if (getActivity() == null || getActivity().isFinishing()) {
+            return;
+        }
+        Log.d(TAG, "updating adapter");
+        mAdapter.setLoading(false);
+        mAdapter.changeCursor(cursor);
+        if (mScrollToTop) {
+            final ListView listView = getListView();
+            if (listView.getFirstVisiblePosition() > 5) {
+                listView.setSelection(5);
+            }
+            listView.smoothScrollToPosition(0);
+            mScrollToTop = false;
+        }
+    }
+
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
         return inflater.inflate(R.layout.call_log_fragment, container, false);
@@ -898,24 +1132,12 @@
     }
 
     private void resetNewCallsFlag() {
-        // Mark all "new" missed calls as not new anymore
-        StringBuilder where = new StringBuilder("type=");
-        where.append(Calls.MISSED_TYPE);
-        where.append(" AND new=1");
-
-        ContentValues values = new ContentValues(1);
-        values.put(Calls.NEW, "0");
-        mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
-                values, where.toString(), null);
+        mCallLogQueryHandler.updateMissedCalls();
     }
 
     private void startQuery() {
         mAdapter.setLoading(true);
-
-        // Cancel any pending queries
-        mQueryHandler.cancelOperation(QUERY_TOKEN);
-        mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
-                CallLogQuery._PROJECTION, null, null, Calls.DEFAULT_SORT_ORDER);
+        mCallLogQueryHandler.fetchCalls();
     }
 
     @Override
diff --git a/src/com/android/contacts/calllog/CallLogListItemHelper.java b/src/com/android/contacts/calllog/CallLogListItemHelper.java
index a8894da..4011929 100644
--- a/src/com/android/contacts/calllog/CallLogListItemHelper.java
+++ b/src/com/android/contacts/calllog/CallLogListItemHelper.java
@@ -57,10 +57,12 @@
      * @param views the views to populate
      * @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
+     * @param isHighlighted whether to use the highlight text for the call
      */
     public void setPhoneCallDetails(CallLogListItemViews views, PhoneCallDetails details,
-            boolean useIcons) {
-        mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details, useIcons);
+            boolean useIcons, boolean isHighlighted) {
+        mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details, useIcons,
+                isHighlighted);
         if (views.callView != null) {
             // The type of icon, call or play, is determined by the first call in the group.
             views.callView.setImageDrawable(
diff --git a/src/com/android/contacts/calllog/CallLogListItemViews.java b/src/com/android/contacts/calllog/CallLogListItemViews.java
index 0cf15cc..75c54a4 100644
--- a/src/com/android/contacts/calllog/CallLogListItemViews.java
+++ b/src/com/android/contacts/calllog/CallLogListItemViews.java
@@ -22,6 +22,7 @@
 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.
@@ -33,22 +34,37 @@
     public final ImageView callView;
     /** The details of the phone call. */
     public final PhoneCallDetailsViews phoneCallDetailsViews;
+    /** The item view for a stand-alone row, or null for other types of rows. */
+    public final View standAloneItemView;
+    /** The header view for a stand-alone row, or null for other types of rows. */
+    public final View standAloneHeaderView;
+    /** The text of the header in a stand-alone row, or null for other types of rows. */
+    public final TextView standAloneHeaderTextView;
 
     private CallLogListItemViews(QuickContactBadge photoView, ImageView callView,
-            PhoneCallDetailsViews phoneCallDetailsViews) {
+            PhoneCallDetailsViews phoneCallDetailsViews, View standAloneItemView,
+            View standAloneHeaderView, TextView standAloneHeaderTextView) {
         this.photoView = photoView;
         this.callView = callView;
         this.phoneCallDetailsViews = phoneCallDetailsViews;
+        this.standAloneItemView = standAloneItemView;
+        this.standAloneHeaderView = standAloneHeaderView;
+        this.standAloneHeaderTextView = standAloneHeaderTextView;
     }
 
     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));
+                PhoneCallDetailsViews.fromView(view),
+                view.findViewById(R.id.call_log_item),
+                view.findViewById(R.id.call_log_header),
+                (TextView) view.findViewById(R.id.call_log_header_text));
     }
 
     public static CallLogListItemViews createForTest(QuickContactBadge photoView,
-            ImageView callView, PhoneCallDetailsViews phoneCallDetailsViews) {
-        return new CallLogListItemViews(photoView, callView, phoneCallDetailsViews);
+            ImageView callView, PhoneCallDetailsViews phoneCallDetailsViews,
+            View standAloneItemView, View standAloneHeaderView, TextView standAloneHeaderTextView) {
+        return new CallLogListItemViews(photoView, callView, phoneCallDetailsViews,
+                standAloneItemView, standAloneHeaderView, standAloneHeaderTextView);
     }
 }
diff --git a/src/com/android/contacts/calllog/CallTypeHelper.java b/src/com/android/contacts/calllog/CallTypeHelper.java
index b06a1c1..0c2068e 100644
--- a/src/com/android/contacts/calllog/CallTypeHelper.java
+++ b/src/com/android/contacts/calllog/CallTypeHelper.java
@@ -19,8 +19,13 @@
 import com.android.contacts.R;
 
 import android.content.res.Resources;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.provider.CallLog.Calls;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
 
 /**
  * Helper class to perform operations related to call types.
@@ -35,13 +40,17 @@
     /** Icon for voicemails. */
     private final Drawable mVoicemailDrawable;
     /** Name used to identify incoming calls. */
-    private final String mIncomingName;
+    private final CharSequence mIncomingName;
     /** Name used to identify outgoing calls. */
-    private final String mOutgoingName;
+    private final CharSequence mOutgoingName;
     /** Name used to identify missed calls. */
-    private final String mMissedName;
+    private final CharSequence mMissedName;
     /** Name used to identify voicemail calls. */
-    private final String mVoicemailName;
+    private final CharSequence mVoicemailName;
+    /** Name used to identify new missed calls. */
+    private final CharSequence mNewMissedName;
+    /** Name used to identify new voicemail calls. */
+    private final CharSequence mNewVoicemailName;
 
     public CallTypeHelper(Resources resources, Drawable incomingDrawable, Drawable outgoingDrawable,
             Drawable missedDrawable, Drawable voicemailDrawable) {
@@ -54,10 +63,14 @@
         mOutgoingName = resources.getString(R.string.type_outgoing);
         mMissedName = resources.getString(R.string.type_missed);
         mVoicemailName = resources.getString(R.string.type_voicemail);
+        mNewMissedName = addBoldAndColor(mMissedName,
+                resources.getColor(R.color.call_log_missed_call_highlight_color));
+        mNewVoicemailName = addBoldAndColor(mVoicemailName,
+                resources.getColor(R.color.call_log_voicemail_highlight_color));
     }
 
     /** Returns the text used to represent the given call type. */
-    public String getCallTypeText(int callType) {
+    public CharSequence getCallTypeText(int callType) {
         switch (callType) {
             case Calls.INCOMING_TYPE:
                 return mIncomingName;
@@ -76,6 +89,28 @@
         }
     }
 
+    /** Returns the text used to represent the given call type. */
+    public CharSequence getHighlightedCallTypeText(int callType) {
+        switch (callType) {
+            case Calls.INCOMING_TYPE:
+                // New incoming calls are not highlighted.
+                return mIncomingName;
+
+            case Calls.OUTGOING_TYPE:
+                // New outgoing calls are not highlighted.
+                return mOutgoingName;
+
+            case Calls.MISSED_TYPE:
+                return mNewMissedName;
+
+            case Calls.VOICEMAIL_TYPE:
+                return mNewVoicemailName;
+
+            default:
+                throw new IllegalArgumentException("invalid call type: " + callType);
+        }
+    }
+
     /** Returns the drawable of the icon associated with the given call type. */
     public Drawable getCallTypeDrawable(int callType) {
         switch (callType) {
@@ -95,4 +130,13 @@
                 throw new IllegalArgumentException("invalid call type: " + callType);
         }
     }
+
+    /** Creates a SpannableString for the given text which is bold and in the given color. */
+    private CharSequence addBoldAndColor(CharSequence text, int color) {
+        int flags = Spanned.SPAN_INCLUSIVE_INCLUSIVE;
+        SpannableString result = new SpannableString(text);
+        result.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(), flags);
+        result.setSpan(new ForegroundColorSpan(color), 0, text.length(), flags);
+        return result;
+    }
 }
diff --git a/src/com/android/contacts/calllog/ExtendedCursor.java b/src/com/android/contacts/calllog/ExtendedCursor.java
new file mode 100644
index 0000000..b17c018
--- /dev/null
+++ b/src/com/android/contacts/calllog/ExtendedCursor.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.calllog;
+
+import com.android.common.io.MoreCloseables;
+
+import android.database.AbstractCursor;
+import android.database.Cursor;
+
+/**
+ * Wraps a cursor to add an additional column with the same value for all rows.
+ * <p>
+ * The number of rows in the cursor and the set of columns is determined by the cursor being
+ * wrapped.
+ */
+public class ExtendedCursor extends AbstractCursor {
+    /** The cursor to wrap. */
+    private final Cursor mCursor;
+    /** The name of the additional column. */
+    private final String mColumnName;
+    /** The value to be assigned to the additional column. */
+    private final Object mValue;
+
+    /**
+     * Creates a new cursor which extends the given cursor by adding a column with a constant value.
+     *
+     * @param cursor the cursor to extend
+     * @param columnName the name of the additional column
+     * @param value the value to be assigned to the additional column
+     */
+    public ExtendedCursor(Cursor cursor, String columnName, Object value) {
+        mCursor = cursor;
+        mColumnName = columnName;
+        mValue = value;
+    }
+
+    @Override
+    public int getCount() {
+        return mCursor.getCount();
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        String[] columnNames = mCursor.getColumnNames();
+        int length = columnNames.length;
+        String[] extendedColumnNames = new String[length + 1];
+        System.arraycopy(columnNames, 0, extendedColumnNames, 0, length);
+        extendedColumnNames[length] = mColumnName;
+        return extendedColumnNames;
+    }
+
+    @Override
+    public String getString(int column) {
+        if (column == mCursor.getColumnCount()) {
+            return (String) mValue;
+        }
+        return mCursor.getString(column);
+    }
+
+    @Override
+    public short getShort(int column) {
+        if (column == mCursor.getColumnCount()) {
+            return (Short) mValue;
+        }
+        return mCursor.getShort(column);
+    }
+
+    @Override
+    public int getInt(int column) {
+        if (column == mCursor.getColumnCount()) {
+            return (Integer) mValue;
+        }
+        return mCursor.getInt(column);
+    }
+
+    @Override
+    public long getLong(int column) {
+        if (column == mCursor.getColumnCount()) {
+            return (Long) mValue;
+        }
+        return mCursor.getLong(column);
+    }
+
+    @Override
+    public float getFloat(int column) {
+        if (column == mCursor.getColumnCount()) {
+            return (Float) mValue;
+        }
+        return mCursor.getFloat(column);
+    }
+
+    @Override
+    public double getDouble(int column) {
+        if (column == mCursor.getColumnCount()) {
+            return (Double) mValue;
+        }
+        return mCursor.getDouble(column);
+    }
+
+    @Override
+    public boolean isNull(int column) {
+        if (column == mCursor.getColumnCount()) {
+            return mValue == null;
+        }
+        return mCursor.isNull(column);
+    }
+
+    @Override
+    public boolean onMove(int oldPosition, int newPosition) {
+        return mCursor.moveToPosition(newPosition);
+    }
+
+    @Override
+    public void close() {
+        MoreCloseables.closeQuietly(mCursor);
+        super.close();
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index 87d4fd6..717db46 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -101,8 +101,6 @@
     private View mDelete;
     private ToneGenerator mToneGenerator;
     private Object mToneGeneratorLock = new Object();
-    private Drawable mDigitsBackground;
-    private Drawable mDigitsEmptyBackground;
     private View mDialpad;
     private View mAdditionalButtonsRow;
 
@@ -216,14 +214,9 @@
             mDigits.getText().clear();
         }
 
-        // Previously we changed background color depending on its text status. Now we don't.
-        // TODO: remove the relevant codes entirely once we're sure we won't use them.
-        /*if (!isDigitsEmpty()) {
-            mDigits.setBackgroundDrawable(mDigitsBackground);
-        } else {
+        if (isDigitsEmpty()) {
             mDigits.setCursorVisible(false);
-            mDigits.setBackgroundDrawable(mDigitsEmptyBackground);
-        }*/
+        }
 
         updateDialAndDeleteButtonEnabledState();
     }
@@ -250,8 +243,6 @@
 
         // Load up the resources for the text field.
         Resources r = getResources();
-        mDigitsBackground = r.getDrawable(R.drawable.btn_dial_textfield_active);
-        mDigitsEmptyBackground = r.getDrawable(R.drawable.btn_dial_textfield);
 
         mDigits = (EditText) fragmentView.findViewById(R.id.digits);
         mDigits.setKeyListener(DialerKeyListener.getInstance());
diff --git a/src/com/android/contacts/group/GroupEditorFragment.java b/src/com/android/contacts/group/GroupEditorFragment.java
index 76f48a3..da57c82 100644
--- a/src/com/android/contacts/group/GroupEditorFragment.java
+++ b/src/com/android/contacts/group/GroupEditorFragment.java
@@ -238,20 +238,11 @@
                 // Account specified in Intent
                 mAccountName = account.name;
                 mAccountType = account.type;
-                setupAccountHeader();
+                setupEditorForAccount();
             } else {
                 // No Account specified. Let the user choose from a disambiguation dialog.
                 selectAccountAndCreateGroup();
             }
-
-            mStatus = Status.EDITING;
-
-            // The user wants to create a new group, temporarily hide the "add members" text view
-            // TODO: Need to allow users to add members if it's a new group. Under the current
-            // approach, we can't add members because it needs a group ID in order to save,
-            // and we don't have a group ID for a new group until the whole group is saved.
-            // Take this out when batch add/remove members is working.
-            mAutoCompleteTextView.setVisibility(View.GONE);
         } else {
             throw new IllegalArgumentException("Unknown Action String " + mAction +
                     ". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT);
@@ -278,7 +269,7 @@
         if (accounts.size() == 1) {
             mAccountName = accounts.get(0).name;
             mAccountType = accounts.get(0).type;
-            setupAccountHeader();
+            setupEditorForAccount();
             return;  // Don't show a dialog.
         }
 
@@ -292,7 +283,7 @@
     public void onAccountChosen(int requestCode, Account account) {
         mAccountName = account.name;
         mAccountType = account.type;
-        setupAccountHeader();
+        setupEditorForAccount();
     }
 
     @Override
@@ -304,9 +295,10 @@
     }
 
     /**
-     * Sets up the account header.
+     * Sets up the editor based on the group's account name and type.
      */
-    private void setupAccountHeader() {
+    private void setupEditorForAccount() {
+        // Setup the account header
         final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(mContext);
         final AccountType accountType = accountTypeManager.getAccountType(mAccountType);
         CharSequence accountTypeDisplayLabel = accountType.getDisplayLabel(mContext);
@@ -316,6 +308,31 @@
         }
         mAccountTypeTextView.setText(accountTypeDisplayLabel);
         mAccountIcon.setImageDrawable(accountType.getDisplayIcon(mContext));
+
+        // Setup the autocomplete adapter (for contacts to suggest to add to the group) based on the
+        // account name and type
+        mAutoCompleteAdapter = new SuggestedMemberListAdapter(mContext,
+                android.R.layout.simple_dropdown_item_1line);
+        mAutoCompleteAdapter.setContentResolver(mContentResolver);
+        mAutoCompleteAdapter.setAccountType(mAccountType);
+        mAutoCompleteAdapter.setAccountName(mAccountName);
+        mAutoCompleteTextView.setAdapter(mAutoCompleteAdapter);
+        mAutoCompleteTextView.setOnItemClickListener(new OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                SuggestedMember member = mAutoCompleteAdapter.getItem(position);
+                loadMemberToAddToGroup(member.getRawContactId(),
+                        String.valueOf(member.getContactId()));
+
+                // Update the autocomplete adapter so the contact doesn't get suggested again
+                mAutoCompleteAdapter.addNewMember(member.getContactId());
+
+                // Clear out the text field
+                mAutoCompleteTextView.setText("");
+            }
+        });
+
+        mStatus = Status.EDITING;
     }
 
     public void load(String action, Uri groupUri, Bundle intentExtras) {
@@ -349,7 +366,7 @@
         // focus on the field).
         mGroupNameView.setText(mOriginalGroupName);
         mGroupNameView.setFocusable(!mGroupNameIsReadOnly);
-        setupAccountHeader();
+        setupEditorForAccount();
     }
 
     public void loadMemberToAddToGroup(long rawContactId, String contactId) {
@@ -380,8 +397,7 @@
     }
 
     private boolean revert() {
-        if (mGroupNameView.getText() != null &&
-                mGroupNameView.getText().toString().equals(mOriginalGroupName)) {
+        if (!hasNameChange() && !hasMembershipChange()) {
             doRevertAction();
         } else {
             CancelEditDialogFragment.show(this);
@@ -453,24 +469,20 @@
         }
         Intent saveIntent = null;
         if (mAction == Intent.ACTION_INSERT) {
-            // TODO: Perform similar work to add members at the same time as creating a new group
+            // Create array of raw contact IDs for contacts to add to the group
+            long[] membersToAddArray = convertToArray(mListMembersToAdd);
+
+            // Create the save intent to create the group and add members at the same time
             saveIntent = ContactSaveService.createNewGroupIntent(activity,
                     new Account(mAccountName, mAccountType), mGroupNameView.getText().toString(),
-                    activity.getClass(), GroupEditorActivity.ACTION_SAVE_COMPLETED);
+                    membersToAddArray, activity.getClass(),
+                    GroupEditorActivity.ACTION_SAVE_COMPLETED);
         } else if (mAction == Intent.ACTION_EDIT) {
             // Create array of raw contact IDs for contacts to add to the group
-            int membersToAddCount = mListMembersToAdd.size();
-            long[] membersToAddArray = new long[membersToAddCount];
-            for (int i = 0; i < membersToAddCount; i++) {
-                membersToAddArray[i] = mListMembersToAdd.get(i).getRawContactId();
-            }
+            long[] membersToAddArray = convertToArray(mListMembersToAdd);
 
             // Create array of raw contact IDs for contacts to add to the group
-            int membersToRemoveCount = mListMembersToRemove.size();
-            long[] membersToRemoveArray = new long[membersToRemoveCount];
-            for (int i = 0; i < membersToRemoveCount; i++) {
-                membersToRemoveArray[i] = mListMembersToRemove.get(i).getRawContactId();
-            }
+            long[] membersToRemoveArray = convertToArray(mListMembersToRemove);
 
             // Create the update intent (which includes the updated group name if necessary)
             saveIntent = ContactSaveService.createGroupUpdateIntent(activity, mGroupId,
@@ -555,6 +567,15 @@
         return groupNameFromTextView;
     }
 
+    private static long[] convertToArray(List<Member> listMembers) {
+        int size = listMembers.size();
+        long[] membersArray = new long[size];
+        for (int i = 0; i < size; i++) {
+            membersArray[i] = listMembers.get(i).getRawContactId();
+        }
+        return membersArray;
+    }
+
     private void addExistingMembers(List<Member> members, List<Long> listContactIds) {
         mListToDisplay.addAll(members);
         mMemberListAdapter.notifyDataSetChanged();
@@ -605,7 +626,6 @@
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
-            mStatus = Status.EDITING;
             bindGroupMetaData(data);
 
             // Load existing members
@@ -651,27 +671,6 @@
                 data.close();
             }
 
-            mAutoCompleteAdapter = new SuggestedMemberListAdapter(getActivity(),
-                    android.R.layout.simple_dropdown_item_1line);
-            mAutoCompleteAdapter.setContentResolver(mContentResolver);
-            mAutoCompleteAdapter.setAccountType(mAccountType);
-            mAutoCompleteAdapter.setAccountName(mAccountName);
-            mAutoCompleteTextView.setAdapter(mAutoCompleteAdapter);
-            mAutoCompleteTextView.setOnItemClickListener(new OnItemClickListener() {
-                @Override
-                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                    SuggestedMember member = mAutoCompleteAdapter.getItem(position);
-                    loadMemberToAddToGroup(member.getRawContactId(),
-                            String.valueOf(member.getContactId()));
-
-                    // Update the autocomplete adapter so the contact doesn't get suggested again
-                    mAutoCompleteAdapter.addNewMember(member.getContactId());
-
-                    // Clear out the text field
-                    mAutoCompleteTextView.setText("");
-                }
-            });
-
             // Update the display list
             addExistingMembers(listExistingMembers, listContactIds);
 
diff --git a/src/com/android/contacts/interactions/GroupCreationDialogFragment.java b/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
index 8991928..87d83b4 100644
--- a/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
+++ b/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
@@ -60,6 +60,7 @@
         Activity activity = getActivity();
         activity.startService(ContactSaveService.createNewGroupIntent(activity,
                 new Account(accountName, accountType), groupLabel,
+                null /* no new members to add */,
                 activity.getClass(), Intent.ACTION_EDIT));
     }
 }
diff --git a/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java b/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
index 4f95563..b628a5e 100644
--- a/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
+++ b/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
@@ -213,7 +213,7 @@
         mHelper.setPhoneCallDetails(mViews,
                 new PhoneCallDetails(number, formattedNumber, new int[]{ Calls.INCOMING_TYPE },
                         TEST_DATE, TEST_DURATION),
-                false);
+                false, false);
     }
 
     /** Sets the phone call details with default values and the given date. */
@@ -221,7 +221,7 @@
         mHelper.setPhoneCallDetails(mViews,
                 new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER,
                         new int[]{ Calls.INCOMING_TYPE }, date, TEST_DURATION),
-                false);
+                false, false);
     }
 
     /** Sets the phone call details with default values and the given call types using icons. */
@@ -238,6 +238,6 @@
         mHelper.setPhoneCallDetails(mViews,
                 new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, callTypes, TEST_DATE,
                         TEST_DURATION),
-                useIcons);
+                useIcons, false);
     }
 }
diff --git a/tests/src/com/android/contacts/activities/CallLogActivityTests.java b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
index 4004227..dfb6d0c 100644
--- a/tests/src/com/android/contacts/activities/CallLogActivityTests.java
+++ b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
@@ -55,13 +55,14 @@
         extends ActivityInstrumentationTestCase2<CallLogActivity> {
     private static final String TAG = "CallLogActivityTests";
 
-    private static final String[] CALL_LOG_PROJECTION = new String[] {
+    private static final String[] EXTENDED_CALL_LOG_PROJECTION = new String[] {
             Calls._ID,
             Calls.NUMBER,
             Calls.DATE,
             Calls.DURATION,
             Calls.TYPE,
             Calls.COUNTRY_ISO,
+            CallLogFragment.CallLogQuery.SECTION_NAME,
     };
     private static final int RAND_DURATION = -1;
     private static final long NOW = -1L;
@@ -123,7 +124,7 @@
         mAdapter.disableRequestProcessingForTest();
         mAdapter.stopRequestProcessing();
         mParentView = new FrameLayout(mActivity);
-        mCursor = new MatrixCursor(CALL_LOG_PROJECTION);
+        mCursor = new MatrixCursor(EXTENDED_CALL_LOG_PROJECTION);
         buildIconMap();
     }
 
@@ -431,6 +432,7 @@
         }
         row.add(type);  // type
         row.add(TEST_COUNTRY_ISO);  // country ISO
+        row.add(CallLogFragment.CallLogQuery.SECTION_OLD_ITEM);  // section
     }
 
     /**
diff --git a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
index b311454..ae8906f 100644
--- a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
+++ b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
@@ -87,7 +87,8 @@
         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)));
+                        new TextView(context), new TextView(context)),
+                new View(context), new View(context), new TextView(context));
     }
 
     @Override
@@ -135,7 +136,7 @@
         mHelper.setPhoneCallDetails(mViews,
                 new PhoneCallDetails(number, formattedNumber, new int[]{ Calls.INCOMING_TYPE },
                         TEST_DATE, TEST_DURATION),
-                true);
+                true, false);
     }
 
     /** Sets the details of a phone call using the specified call type. */
@@ -143,6 +144,6 @@
         mHelper.setPhoneCallDetails(mViews,
                 new PhoneCallDetails(
                         TEST_NUMBER, TEST_FORMATTED_NUMBER, types, TEST_DATE, TEST_DURATION),
-                true);
+                true, false);
     }
 }