Merge "[Phone] New phone favorite tab."
diff --git a/res/drawable-hdpi/ic_dial_action_search.png b/res/drawable-hdpi/ic_dial_action_search.png
new file mode 100644
index 0000000..898ce11
--- /dev/null
+++ b/res/drawable-hdpi/ic_dial_action_search.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_dial_action_search.png b/res/drawable-mdpi/ic_dial_action_search.png
new file mode 100644
index 0000000..88cba92
--- /dev/null
+++ b/res/drawable-mdpi/ic_dial_action_search.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_dial_action_search.png b/res/drawable-xhdpi/ic_dial_action_search.png
new file mode 100644
index 0000000..fe3aa24
--- /dev/null
+++ b/res/drawable-xhdpi/ic_dial_action_search.png
Binary files differ
diff --git a/res/layout/account_filter_header.xml b/res/layout/account_filter_header.xml
new file mode 100644
index 0000000..4d45d25
--- /dev/null
+++ b/res/layout/account_filter_header.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Layout showing the type of account filter
+     (e.g. All contacts filter, custom filter, etc.),
+     which is the header of all contact lists. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/account_filter_header_container"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?attr/list_item_header_height"
+    android:orientation="vertical"
+    android:paddingTop="@dimen/contact_browser_list_top_margin"
+    android:layout_marginLeft="@dimen/contact_browser_list_header_left_margin"
+    android:layout_marginRight="@dimen/contact_browser_list_header_right_margin"
+    android:background="?android:attr/selectableItemBackground"
+    android:visibility="gone">
+    <TextView
+        android:id="@+id/account_filter_header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="end"
+        android:textStyle="bold"
+        android:textAllCaps="true"
+        android:paddingLeft="@dimen/contact_browser_list_item_text_indent"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorSecondary" />
+    <View
+        android:id="@+id/account_filter_header_bottom_divider"
+        android:layout_height="1dip"
+        style="@style/SectionDivider" />
+</LinearLayout>
diff --git a/res/layout/contacts_list_content.xml b/res/layout/contacts_list_content.xml
index dfa99f6..05f1930 100644
--- a/res/layout/contacts_list_content.xml
+++ b/res/layout/contacts_list_content.xml
@@ -29,33 +29,9 @@
 
     <!-- Shown only when an Account filter is set.
          - paddingTop should be here to show "shade" effect correctly. -->
-    <LinearLayout
+    <include
         android:id="@+id/account_filter_header_container"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?attr/list_item_header_height"
-        android:orientation="vertical"
-        android:paddingTop="@dimen/contact_browser_list_top_margin"
-        android:layout_marginLeft="@dimen/contact_browser_list_header_left_margin"
-        android:layout_marginRight="@dimen/contact_browser_list_header_right_margin"
-        android:background="?android:attr/selectableItemBackground"
-        android:visibility="gone">
-        <TextView
-            android:id="@+id/account_filter_header"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:textStyle="bold"
-            android:textAllCaps="true"
-            android:paddingLeft="@dimen/contact_browser_list_item_text_indent"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:attr/textColorSecondary" />
-        <View
-            android:id="@+id/account_filter_header_bottom_divider"
-            android:layout_height="1dip"
-            style="@style/SectionDivider" />
-    </LinearLayout>
+        layout="@layout/account_filter_header" />
 
     <view
         class="com.android.contacts.widget.PinnedHeaderListView"
diff --git a/res/layout/dialpad_additional_buttons.xml b/res/layout/dialpad_additional_buttons.xml
index 2d42ad0..836187e 100644
--- a/res/layout/dialpad_additional_buttons.xml
+++ b/res/layout/dialpad_additional_buttons.xml
@@ -34,7 +34,7 @@
         android:state_enabled="false"
         android:background="?android:attr/selectableItemBackground"
         android:contentDescription="@string/description_search_button"
-        android:src="@drawable/ic_see_contacts_holo_dark"/>
+        android:src="@drawable/ic_dial_action_search"/>
 
     <View
         android:layout_width="1dip"
diff --git a/res/menu/dialtacts_options.xml b/res/menu/dialtacts_options.xml
index f653242..54ca086 100644
--- a/res/menu/dialtacts_options.xml
+++ b/res/menu/dialtacts_options.xml
@@ -17,6 +17,7 @@
     <item
         android:id="@+id/search_on_action_bar"
         android:title="@string/menu_all_contacts"
+        android:icon="@drawable/ic_dial_action_search"
         android:showAsAction="ifRoom" />
 
     <!-- This should come after the other menus in CallLog and Dialpad -->
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index baa4b4b..f06b227 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -27,6 +27,7 @@
 import com.android.contacts.list.ContactTileAdapter.DisplayType;
 import com.android.contacts.list.ContactTileListFragment;
 import com.android.contacts.list.OnPhoneNumberPickerActionListener;
+import com.android.contacts.list.PhoneFavoriteFragment;
 import com.android.contacts.list.PhoneNumberPickerFragment;
 import com.android.internal.telephony.ITelephony;
 
@@ -120,7 +121,7 @@
     public class ViewPagerAdapter extends FragmentPagerAdapter {
         private DialpadFragment mDialpadFragment;
         private CallLogFragment mCallLogFragment;
-        private ContactTileListFragment mContactTileListFragment;
+        private PhoneFavoriteFragment mPhoneFavoriteFragment;
 
         public ViewPagerAdapter(FragmentManager fm) {
             super(fm);
@@ -140,10 +141,10 @@
                     }
                     return mCallLogFragment;
                 case TAB_INDEX_FAVORITES:
-                    if (mContactTileListFragment == null) {
-                        mContactTileListFragment = new ContactTileListFragment();
+                    if (mPhoneFavoriteFragment == null) {
+                        mPhoneFavoriteFragment = new PhoneFavoriteFragment();
                     }
-                    return mContactTileListFragment;
+                    return mPhoneFavoriteFragment;
             }
             throw new IllegalStateException("No fragment at position " + position);
         }
@@ -206,14 +207,13 @@
     }
 
     private String mFilterText;
-    private Uri mDialUri;
 
     /** Enables horizontal swipe between Fragments. */
     private ViewPager mViewPager;
     private final PageChangeListener mPageChangeListener = new PageChangeListener();
     private DialpadFragment mDialpadFragment;
     private CallLogFragment mCallLogFragment;
-    private ContactTileListFragment mStrequentFragment;
+    private PhoneFavoriteFragment mPhoneFavoriteFragment;
 
     private final TabListener mTabListener = new TabListener() {
         @Override
@@ -373,13 +373,23 @@
         mContactListFilterController.addListener(new ContactListFilterListener() {
             @Override
             public void onContactListFilterChanged() {
-                if (mSearchFragment == null || !mSearchFragment.isAdded()) {
-                    Log.w(TAG, "Search Fragment isn't available when ContactListFilter is changed");
-                    return;
-                }
-                mSearchFragment.setFilter(mContactListFilterController.getFilter());
+                boolean doInvalidateOptionsMenu = false;
 
-                invalidateOptionsMenu();
+                if (mPhoneFavoriteFragment != null && mPhoneFavoriteFragment.isAdded()) {
+                    mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
+                    doInvalidateOptionsMenu = true;
+                }
+
+                if (mSearchFragment != null && mSearchFragment.isAdded()) {
+                    mSearchFragment.setFilter(mContactListFilterController.getFilter());
+                    doInvalidateOptionsMenu = true;
+                } else {
+                    Log.w(TAG, "Search Fragment isn't available when ContactListFilter is changed");
+                }
+
+                if (doInvalidateOptionsMenu) {
+                    invalidateOptionsMenu();
+                }
             }
         });
 
@@ -423,6 +433,9 @@
         // ContactListFilterController and they should be able to receive filter change event
         // from the same controller (Bug 5165507)
         mContactListFilterController.onStart(true);
+        if (mPhoneFavoriteFragment != null) {
+            mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
+        }
         if (mSearchFragment != null) {
             mSearchFragment.setFilter(mContactListFilterController.getFilter());
         }
@@ -480,11 +493,13 @@
             if (currentPosition == TAB_INDEX_CALL_LOG) {
                 mCallLogFragment.onVisibilityChanged(true);
             }
-        } else if (fragment instanceof ContactTileListFragment) {
-            mStrequentFragment = (ContactTileListFragment) fragment;
-            mStrequentFragment.enableQuickContact(false);
-            mStrequentFragment.setListener(mStrequentListener);
-            mStrequentFragment.setDisplayType(DisplayType.STREQUENT_PHONE_ONLY);
+        } else if (fragment instanceof PhoneFavoriteFragment) {
+            mPhoneFavoriteFragment = (PhoneFavoriteFragment) fragment;
+            mPhoneFavoriteFragment.setListener(mPhoneFavoriteListener);
+            if (mContactListFilterController != null
+                    && mContactListFilterController.getFilter() != null) {
+                mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
+            }
         } else if (fragment instanceof PhoneNumberPickerFragment) {
             mSearchFragment = (PhoneNumberPickerFragment) fragment;
             mSearchFragment.setOnPhoneNumberPickerActionListener(mPhoneNumberPickerActionListener);
@@ -699,8 +714,8 @@
         }
     };
 
-    private ContactTileListFragment.Listener mStrequentListener =
-            new ContactTileListFragment.Listener() {
+    private PhoneFavoriteFragment.Listener mPhoneFavoriteListener =
+            new PhoneFavoriteFragment.Listener() {
         @Override
         public void onContactSelected(Uri contactUri) {
             PhoneNumberInteraction.startInteractionForPhoneCall(
@@ -750,7 +765,13 @@
                 searchMenuItem.setOnMenuItemClickListener(mSearchMenuItemClickListener);
                 showCallSettingsMenu = true;
             }
-            filterOptionMenuItem.setVisible(false);
+            if (tab != null && tab.getPosition() == TAB_INDEX_FAVORITES) {
+                filterOptionMenuItem.setVisible(true);
+                filterOptionMenuItem.setOnMenuItemClickListener(
+                        mFilterOptionsMenuItemClickListener);
+            } else {
+                filterOptionMenuItem.setVisible(false);
+            }
             addContactOptionMenuItem.setVisible(false);
 
             if (showCallSettingsMenu) {
@@ -814,9 +835,6 @@
         // layout instead of asking the search menu item to take care of SearchView.
         mSearchView.onActionViewExpanded();
         mInSearchUi = true;
-
-        // Clear focus and suppress keyboard show-up.
-        mSearchView.clearFocus();
     }
 
     private void showInputMethod(View view) {
@@ -872,7 +890,7 @@
             case TAB_INDEX_CALL_LOG:
                 return mCallLogFragment;
             case TAB_INDEX_FAVORITES:
-                return mStrequentFragment;
+                return mPhoneFavoriteFragment;
             default:
                 throw new IllegalStateException("Unknown fragment index: " + position);
         }
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index 70493c5..295c2a1 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -74,10 +74,6 @@
     // Style values for layout and appearance
     private final int mPreferredHeight;
     private final int mVerticalDividerMargin;
-    private final int mPaddingTop;
-    private final int mPaddingRight;
-    private final int mPaddingBottom;
-    private final int mPaddingLeft;
     private final int mGapBetweenImageAndText;
     private final int mGapBetweenLabelAndData;
     private final int mCallButtonPadding;
@@ -93,11 +89,31 @@
     private final int mTextIndent;
     private Drawable mActivatedBackgroundDrawable;
 
+    // In the future we may need to merge these local padding to View's mPaddingXXX
+    private final int mExtraPaddingTop;
+    private final int mExtraPaddingBottom;
+    private int mExtraPaddingLeft;
+    private int mExtraPaddingRight;
+
+    // Will be used with adjustListItemSelectionBounds().
+    private int mSelectionBoundsMarginLeft;
+    private int mSelectionBoundsMarginRight;
+
     // Horizontal divider between contact views.
     private boolean mHorizontalDividerVisible = true;
     private Drawable mHorizontalDividerDrawable;
     private int mHorizontalDividerHeight;
 
+    /**
+     * Where to put contact photo. This affects the other Views' layout or look-and-feel.
+     */
+    public enum PhotoPosition {
+        LEFT,
+        RIGHT
+    }
+    public static final PhotoPosition DEFAULT_PHOTO_POSITION = PhotoPosition.RIGHT;
+    private PhotoPosition mPhotoPosition = DEFAULT_PHOTO_POSITION;
+
     // Vertical divider between the call icon and the text.
     private boolean mVerticalDividerVisible;
     private Drawable mVerticalDividerDrawable;
@@ -209,13 +225,13 @@
                 R.styleable.ContactListItemView_list_item_divider);
         mVerticalDividerMargin = a.getDimensionPixelOffset(
                 R.styleable.ContactListItemView_list_item_vertical_divider_margin, 0);
-        mPaddingTop = a.getDimensionPixelOffset(
+        mExtraPaddingTop = a.getDimensionPixelOffset(
                 R.styleable.ContactListItemView_list_item_padding_top, 0);
-        mPaddingBottom = a.getDimensionPixelOffset(
+        mExtraPaddingBottom = a.getDimensionPixelOffset(
                 R.styleable.ContactListItemView_list_item_padding_bottom, 0);
-        mPaddingLeft = a.getDimensionPixelOffset(
+        mExtraPaddingLeft = a.getDimensionPixelOffset(
                 R.styleable.ContactListItemView_list_item_padding_left, 0);
-        mPaddingRight = a.getDimensionPixelOffset(
+        mExtraPaddingRight = a.getDimensionPixelOffset(
                 R.styleable.ContactListItemView_list_item_padding_right, 0);
         mGapBetweenImageAndText = a.getDimensionPixelOffset(
                 R.styleable.ContactListItemView_list_item_gap_between_image_and_text, 0);
@@ -336,7 +352,8 @@
 
         // Calculate height including padding
         height += mNameTextViewHeight + mPhoneticNameTextViewHeight + mLabelTextViewHeight +
-                mSnippetTextViewHeight + mStatusTextViewHeight + mPaddingTop + mPaddingBottom;
+                mSnippetTextViewHeight + mStatusTextViewHeight +
+                mExtraPaddingTop + mExtraPaddingBottom;
 
         if (isVisible(mCallButton)) {
             mCallButton.measure(0, 0);
@@ -344,7 +361,7 @@
 
         // Make sure the height is at least as high as the photo
         ensurePhotoViewSize();
-        height = Math.max(height, mPhotoViewHeight + mPaddingBottom + mPaddingTop);
+        height = Math.max(height, mPhotoViewHeight + mExtraPaddingBottom + mExtraPaddingTop);
 
         // Add horizontal divider height
         if (mHorizontalDividerVisible) {
@@ -375,29 +392,30 @@
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        int height = bottom - top;
-        int width = right - left;
+        final int height = bottom - top;
+        final int width = right - left;
 
         // Determine the vertical bounds by laying out the header first.
         int topBound = 0;
         int bottomBound = height;
-        int leftBound = mPaddingLeft;
+        int leftBound = mExtraPaddingLeft;
+        int rightBound = width - mExtraPaddingRight;
 
         // Put the header in the top of the contact view (Text + underline view)
         if (mHeaderVisible) {
             mHeaderTextView.layout(leftBound + mHeaderTextIndent,
                     0,
-                    width - mPaddingRight,
+                    rightBound,
                     mHeaderBackgroundHeight);
             if (mCountView != null) {
-                mCountView.layout(width - mPaddingRight - mCountView.getMeasuredWidth(),
+                mCountView.layout(width - mExtraPaddingRight - mCountView.getMeasuredWidth(),
                         0,
-                        width - mPaddingRight,
+                        rightBound,
                         mHeaderBackgroundHeight);
             }
             mHeaderDivider.layout(leftBound,
                     mHeaderBackgroundHeight,
-                    width - mPaddingRight,
+                    rightBound,
                     mHeaderBackgroundHeight + mHeaderUnderlineHeight);
             topBound += (mHeaderBackgroundHeight + mHeaderUnderlineHeight);
         }
@@ -407,7 +425,7 @@
             mHorizontalDividerDrawable.setBounds(
                     leftBound,
                     height - mHorizontalDividerHeight,
-                    width - mPaddingRight,
+                    rightBound,
                     height);
             bottomBound -= mHorizontalDividerHeight;
         }
@@ -418,26 +436,55 @@
             mActivatedBackgroundDrawable.setBounds(mBoundsWithoutHeader);
         }
 
-        // Set the top/bottom paddings
-        topBound += mPaddingTop;
-        bottomBound -= mPaddingBottom;
+        // Set the top/bottom padding
+        topBound += mExtraPaddingTop;
+        bottomBound -= mExtraPaddingBottom;
 
-        // Set contact layout:
-        // Photo on the right, call button to the left of the photo
-        // Text aligned to the left along with the presence status.
+        final View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
+        if (mPhotoPosition == PhotoPosition.LEFT) {
+            // Photo is the left most view. All the other Views should on the right of the photo.
+            if (photoView != null) {
+                // Center the photo vertically
+                final int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
+                photoView.layout(
+                        leftBound,
+                        photoTop,
+                        leftBound + mPhotoViewWidth,
+                        photoTop + mPhotoViewHeight);
+                leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
+            } else if (mKeepHorizontalPaddingForPhotoView) {
+                // Draw nothing but keep the padding.
+                leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
+            }
+        } else {
+            // Photo is the right most view. Right bound should be adjusted that way.
+            if (photoView != null) {
+                // Center the photo vertically
+                final int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
+                photoView.layout(
+                        rightBound - mPhotoViewWidth,
+                        photoTop,
+                        rightBound,
+                        photoTop + mPhotoViewHeight);
+                rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
+            }
 
-        // layout the photo and call button.
-        int rightBound = layoutRightSide(height, topBound, bottomBound, width - mPaddingRight);
+            // Add indent between left-most padding and texts.
+            leftBound += mTextIndent;
+        }
+
+        // Layout the call button.
+        rightBound = layoutRightSide(height, topBound, bottomBound, rightBound);
 
         // Center text vertically
-        int totalTextHeight = mNameTextViewHeight + mPhoneticNameTextViewHeight +
+        final int totalTextHeight = mNameTextViewHeight + mPhoneticNameTextViewHeight +
                 mLabelTextViewHeight + mSnippetTextViewHeight + mStatusTextViewHeight;
         int textTopBound = (bottomBound + topBound - totalTextHeight) / 2;
 
         // Layout all text view and presence icon
         // Put name TextView first
         if (isVisible(mNameTextView)) {
-            mNameTextView.layout(leftBound + mTextIndent,
+            mNameTextView.layout(leftBound,
                     textTopBound,
                     rightBound,
                     textTopBound + mNameTextViewHeight);
@@ -445,13 +492,13 @@
         }
 
         // Presence and status
-        int statusLeftBound = leftBound + mTextIndent;
+        int statusLeftBound = leftBound;
         if (isVisible(mPresenceIcon)) {
             int iconWidth = mPresenceIcon.getMeasuredWidth();
             mPresenceIcon.layout(
-                    leftBound + mTextIndent,
+                    leftBound,
                     textTopBound,
-                    leftBound + iconWidth + mTextIndent,
+                    leftBound + iconWidth,
                     textTopBound + mStatusTextViewHeight);
             statusLeftBound += (iconWidth + mPresenceIconMargin);
         }
@@ -470,7 +517,7 @@
         // Rest of text views
         int dataLeftBound = leftBound;
         if (isVisible(mPhoneticNameTextView)) {
-            mPhoneticNameTextView.layout(leftBound + mTextIndent,
+            mPhoneticNameTextView.layout(leftBound,
                     textTopBound,
                     rightBound,
                     textTopBound + mPhoneticNameTextViewHeight);
@@ -478,12 +525,21 @@
         }
 
         if (isVisible(mLabelView)) {
-            dataLeftBound = leftBound + mLabelView.getMeasuredWidth() + mTextIndent;
-            mLabelView.layout(leftBound + mTextIndent,
-                    textTopBound,
-                    dataLeftBound,
-                    textTopBound + mLabelTextViewHeight);
-            dataLeftBound += mGapBetweenLabelAndData;
+            if (mPhotoPosition == PhotoPosition.LEFT) {
+                // When photo is on left, label is placed on the right edge of the list item.
+                mLabelView.layout(rightBound - mLabelView.getMeasuredWidth(),
+                        textTopBound,
+                        rightBound,
+                        textTopBound + mLabelTextViewHeight);
+            } else {
+                // When photo is on right, label is placed on the left of data view.
+                dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
+                mLabelView.layout(leftBound,
+                        textTopBound,
+                        dataLeftBound,
+                        textTopBound + mLabelTextViewHeight);
+                dataLeftBound += mGapBetweenLabelAndData;
+            }
         }
 
         if (isVisible(mDataView)) {
@@ -497,7 +553,7 @@
         }
 
         if (isVisible(mSnippetView)) {
-            mSnippetView.layout(leftBound + mTextIndent,
+            mSnippetView.layout(leftBound,
                     textTopBound,
                     rightBound,
                     textTopBound + mSnippetTextViewHeight);
@@ -510,21 +566,6 @@
      * @return new right boundary
      */
     protected int layoutRightSide(int height, int topBound, int bottomBound, int rightBound) {
-
-        // Photo is the right most view, set it up
-
-        View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
-        if (photoView != null) {
-            // Center the photo vertically
-            int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
-            photoView.layout(
-                    rightBound - mPhotoViewWidth,
-                    photoTop,
-                    rightBound,
-                    photoTop + mPhotoViewHeight);
-            rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
-        }
-
         // Put call button and vertical divider
         if (isVisible(mCallButton)) {
             int buttonWidth = mCallButton.getMeasuredWidth();
@@ -553,6 +594,8 @@
     public void adjustListItemSelectionBounds(Rect bounds) {
         bounds.top += mBoundsWithoutHeader.top;
         bounds.bottom = bounds.top + mBoundsWithoutHeader.height();
+        bounds.left += mSelectionBoundsMarginLeft;
+        bounds.right -= mSelectionBoundsMarginRight;
     }
 
     protected boolean isVisible(View view) {
@@ -874,7 +917,12 @@
             mLabelView.setSingleLine(true);
             mLabelView.setEllipsize(getTextEllipsis());
             mLabelView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
-            mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
+            if (mPhotoPosition == PhotoPosition.LEFT) {
+                mLabelView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mCountViewTextSize);
+                mLabelView.setAllCaps(true);
+            } else {
+                mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
+            }
             mLabelView.setActivated(isActivated());
             addView(mLabelView);
         }
@@ -1175,4 +1223,44 @@
         // view (ListView).
         forceLayout();
     }
+
+    public void setPhotoPosition(PhotoPosition photoPosition) {
+        mPhotoPosition = photoPosition;
+    }
+
+    public PhotoPosition getPhotoPosition() {
+        return mPhotoPosition;
+    }
+
+    /**
+     * Sets custom padding inside this object. Do not use this method without any strong reason.
+     *
+     * Detail: we cannot simply override {@link #setPadding(int, int, int, int)}. {@link View}
+     * does *not* know this view's local padding but has completely different ones.
+     * See View#mPaddingLeft and View#mPaddingRight. View also has View#mUserPaddingLeft, and
+     * View#mUserPaddingRight in addition to View#mPaddingLeft and View#mPaddingRight, to handle
+     * {@link View#setPadding(int, int, int, int)} correctly. If setPadding() is overridden to
+     * reset our {@link #mExtraPaddingLeft} and {@link #mExtraPaddingRight} carelessly, the whole
+     * View layout gets confused.
+     *
+     * To simplify our implementation, this method just modify the local two padding without
+     * confusing its parent.
+     *
+     * If we want to fix this multiple padding issue correctly, we should merge local padding
+     * in this class into View's ones. Also we should remove "list_item_padding_left" and
+     * "list_item_padding_right" attributes, using "android:paddingLeft" and "android:paddingRight".
+     */
+    public void setExtraPadding(int left, int right) {
+        mExtraPaddingLeft = left;
+        mExtraPaddingRight = right;
+    }
+
+    /**
+     * Specifies left and right margin for selection bounds. See also
+     * {@link #adjustListItemSelectionBounds(Rect)}.
+     */
+    public void setSelectionBoundsHorizontalMargin(int left, int right) {
+        mSelectionBoundsMarginLeft = left;
+        mSelectionBoundsMarginRight = right;
+    }
 }
diff --git a/src/com/android/contacts/list/ContactTileAdapter.java b/src/com/android/contacts/list/ContactTileAdapter.java
index a12b127..8de7fd5 100644
--- a/src/com/android/contacts/list/ContactTileAdapter.java
+++ b/src/com/android/contacts/list/ContactTileAdapter.java
@@ -201,6 +201,7 @@
         switch (mDisplayType) {
             case STREQUENT:
             case STREQUENT_PHONE_ONLY:
+                cursor.moveToPosition(-1);
                 while (cursor.moveToNext()) {
                     if (cursor.getInt(mStarredIndex) == 0) {
                         return cursor.getPosition();
@@ -409,7 +410,7 @@
         text.setText(mDisplayType == DisplayType.STREQUENT_PHONE_ONLY ?
                 mContext.getString(R.string.favoritesFrequentCalled) :
                 mContext.getString(R.string.favoritesFrequentContacted));
-       return dividerView;
+        return dividerView;
     }
 
     private int getLayoutResourceId(int viewType) {
@@ -468,6 +469,14 @@
         }
     }
 
+    /**
+     * Returns the "frequent header" position. Only available when STREQUENT or
+     * STREQUENT_PHONE_ONLY is used for its display type.
+     */
+    public int getFrequentHeaderPosition() {
+        return getRowCount(mDividerPosition);
+    }
+
     private ContactTileView.Listener mContactTileListener = new ContactTileView.Listener() {
         @Override
         public void onClick(ContactTileView contactTileView) {
diff --git a/src/com/android/contacts/list/PhoneFavoriteFragment.java b/src/com/android/contacts/list/PhoneFavoriteFragment.java
new file mode 100644
index 0000000..860e368
--- /dev/null
+++ b/src/com/android/contacts/list/PhoneFavoriteFragment.java
@@ -0,0 +1,436 @@
+/*
+ * 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.list;
+
+import com.android.contacts.ContactPhotoManager;
+import com.android.contacts.ContactTileLoaderFactory;
+import com.android.contacts.R;
+import com.android.contacts.preference.ContactsPreferences;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Directory;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * Fragment for Phone UI's favorite screen.
+ *
+ * This fragment contains three kinds of contacts in one screen: "starred", "frequent", and "all"
+ * contacts. To show them at once, this merges results from {@link ContactTileAdapter} and
+ * {@link PhoneNumberListAdapter} into one unified list using {@link PhoneFavoriteMergedAdapter}.
+ * A contact filter header is also inserted between those adapters' results.
+ */
+public class PhoneFavoriteFragment extends Fragment implements OnItemClickListener {
+    private static final String TAG = PhoneFavoriteFragment.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    /**
+     * Used with LoaderManager.
+     */
+    private static int LOADER_ID_CONTACT_TILE = 1;
+    private static int LOADER_ID_ALL_CONTACTS = 2;
+
+    private static final String KEY_FILTER = "filter";
+
+    public interface Listener {
+        public void onContactSelected(Uri contactUri);
+    }
+
+    private class ContactTileLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> {
+        @Override
+        public CursorLoader onCreateLoader(int id, Bundle args) {
+            if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onCreateLoader.");
+            return ContactTileLoaderFactory.createStrequentPhoneOnlyLoader(getActivity());
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+            if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoadFinished");
+            mContactTileAdapter.setContactCursor(data);
+
+            if (mAllContactsForceReload) {
+                mAllContactsAdapter.onDataReload();
+                // Use restartLoader() to make LoaderManager to load the section again.
+                getLoaderManager().restartLoader(
+                        LOADER_ID_ALL_CONTACTS, null, mAllContactsLoaderListener);
+            } else if (!mAllContactsLoaderStarted) {
+                // Load "all" contacts if not loaded yet.
+                getLoaderManager().initLoader(
+                        LOADER_ID_ALL_CONTACTS, null, mAllContactsLoaderListener);
+            }
+            mAllContactsForceReload = false;
+            mAllContactsLoaderStarted = true;
+
+            // Show the filter header with "loading" state.
+            updateFilterHeaderView();
+            mAccountFilterHeaderContainer.setVisibility(View.VISIBLE);
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Cursor> loader) {
+            if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoaderReset. ");
+        }
+    }
+
+    private class AllContactsLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> {
+        @Override
+        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+            if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onCreateLoader");
+            CursorLoader loader = new CursorLoader(getActivity(), null, null, null, null, null);
+            mAllContactsAdapter.configureLoader(loader, Directory.DEFAULT);
+            return loader;
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+            if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onLoadFinished");
+            mAllContactsAdapter.changeCursor(0, data);
+            updateFilterHeaderView();
+            mAccountFilterHeaderContainer.setVisibility(View.VISIBLE);
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Cursor> loader) {
+            if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onLoaderReset. ");
+        }
+    }
+
+    private class ContactTileAdapterListener implements ContactTileAdapter.Listener {
+        @Override
+        public void onContactSelected(Uri contactUri) {
+            if (mListener != null) {
+                mListener.onContactSelected(contactUri);
+            }
+        }
+    }
+
+    private class FilterHeaderClickListener implements OnClickListener {
+        @Override
+        public void onClick(View view) {
+            final Activity activity = getActivity();
+            if (activity != null) {
+                final Intent intent = new Intent(activity, AccountFilterActivity.class);
+                activity.startActivityForResult(
+                        intent, AccountFilterActivity.DEFAULT_REQUEST_CODE);
+            }
+        }
+    }
+
+    private class ContactsPreferenceChangeListener
+            implements ContactsPreferences.ChangeListener {
+        @Override
+        public void onChange() {
+            if (loadContactsPreferences()) {
+                requestReloadAllContacts();
+            }
+        }
+    }
+
+    private Listener mListener;
+    private PhoneFavoriteMergedAdapter mAdapter;
+    private ContactTileAdapter mContactTileAdapter;
+    private PhoneNumberListAdapter mAllContactsAdapter;
+
+    /**
+     * true when the loader for {@link PhoneNumberListAdapter} has started already.
+     */
+    private boolean mAllContactsLoaderStarted;
+    /**
+     * true when the loader for {@link PhoneNumberListAdapter} must reload "all" contacts again.
+     * It typically happens when {@link ContactsPreferences} has changed its settings
+     * (display order and sort order)
+     */
+    private boolean mAllContactsForceReload;
+
+    private SharedPreferences mPrefs;
+    private ContactsPreferences mContactsPrefs;
+    private ContactListFilter mFilter;
+
+    private TextView mEmptyView;
+    private ListView mListView;
+    private View mAccountFilterHeaderContainer;
+    private TextView mAccountFilterHeaderView;
+
+    private final ContactTileAdapter.Listener mContactTileAdapterListener =
+            new ContactTileAdapterListener();
+    private final LoaderManager.LoaderCallbacks<Cursor> mContactTileLoaderListener =
+            new ContactTileLoaderListener();
+    private final LoaderManager.LoaderCallbacks<Cursor> mAllContactsLoaderListener =
+            new AllContactsLoaderListener();
+    private final OnClickListener mFilterHeaderClickListener = new FilterHeaderClickListener();
+    private final ContactsPreferenceChangeListener mContactsPreferenceChangeListener =
+            new ContactsPreferenceChangeListener();
+
+    @Override
+    public void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        if (savedState != null) {
+            mFilter = savedState.getParcelable(KEY_FILTER);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelable(KEY_FILTER, mFilter);
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(activity);
+        mContactsPrefs = new ContactsPreferences(activity);
+
+        // Create the account filter header but keep it hidden until "all" contacts are loaded.
+        mAccountFilterHeaderContainer = View.inflate(
+                activity, R.layout.account_filter_header, null);
+        mAccountFilterHeaderView =
+                (TextView) mAccountFilterHeaderContainer.findViewById(R.id.account_filter_header);
+        mAccountFilterHeaderContainer.setOnClickListener(mFilterHeaderClickListener);
+        mAccountFilterHeaderContainer.setVisibility(View.GONE);
+
+        initAdapters(activity);
+
+        mAllContactsAdapter.setFilter(mFilter);
+        mAllContactsForceReload = true;
+        updateFilterHeaderView();
+    }
+
+    /**
+     * Constructs and initializes {@link #mContactTileAdapter}, {@link #mAllContactsAdapter}, and
+     * {@link #mAllContactsAdapter}.
+     */
+    private void initAdapters(Context context) {
+        mContactTileAdapter = new ContactTileAdapter(context, mContactTileAdapterListener,
+                getResources().getInteger(R.integer.contact_tile_column_count),
+                ContactTileAdapter.DisplayType.STREQUENT_PHONE_ONLY);
+        mContactTileAdapter.setPhotoLoader(ContactPhotoManager.getInstance(context));
+
+        // Setup the "all" adapter manually. See also the setup logic in ContactEntryListFragment.
+        mAllContactsAdapter = new PhoneNumberListAdapter(context);
+        mAllContactsAdapter.setDisplayPhotos(true);
+        mAllContactsAdapter.setQuickContactEnabled(true);
+        mAllContactsAdapter.setSearchMode(false);
+        mAllContactsAdapter.setIncludeProfile(false);
+        mAllContactsAdapter.setSelectionVisible(false);
+        mAllContactsAdapter.setDarkTheme(true);
+        mAllContactsAdapter.setPhotoLoader(ContactPhotoManager.getInstance(context));
+        // Disable directory header.
+        mAllContactsAdapter.setHasHeader(0, false);
+        // Show A-Z section index.
+        mAllContactsAdapter.setSectionHeaderDisplayEnabled(true);
+        // Disable pinned header. It doesn't work with this fragment.
+        mAllContactsAdapter.setPinnedPartitionHeadersEnabled(false);
+        // Put photos on left for consistency with "frequent" contacts section.
+        mAllContactsAdapter.setPhotoPosition(ContactListItemView.PhotoPosition.LEFT);
+
+        mAdapter = new PhoneFavoriteMergedAdapter(
+                context, mContactTileAdapter, mAccountFilterHeaderContainer, mAllContactsAdapter);
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mPrefs = null;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        final View listLayout = inflater.inflate(R.layout.contact_tile_list, container, false);
+
+        mListView = (ListView) listLayout.findViewById(R.id.contact_tile_list);
+
+        mListView.setItemsCanFocus(true);
+        mListView.setAdapter(mAdapter);
+        mListView.setOnItemClickListener(this);
+        mListView.setFastScrollEnabled(true);
+        // We want to hide the scroll bar after a while.
+        mListView.setFastScrollAlwaysVisible(false);
+        mListView.setVerticalScrollBarEnabled(true);
+        mListView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT);
+        mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
+
+        mEmptyView = (TextView) listLayout.findViewById(R.id.contact_tile_list_empty);
+        mEmptyView.setText(getString(R.string.listTotalAllContactsZero));
+        mListView.setEmptyView(mEmptyView);
+
+        updateFilterHeaderView();
+
+        return listLayout;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        mContactsPrefs.registerChangeListener(mContactsPreferenceChangeListener);
+
+        // If ContactsPreferences has changed, we need to reload "all" contacts with the new
+        // settings. If mAllContactsFoarceReload is already true, it should be kept.
+        if (loadContactsPreferences()) {
+            mAllContactsForceReload = true;
+        }
+
+        // Use initLoader() instead of reloadLoader() to refraing unnecessary reload.
+        // This method call implicitly assures ContactTileLoaderListener's onLoadFinished() will
+        // be called, on which we'll check if "all" contacts should be reloaded again or not.
+        getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, mContactTileLoaderListener);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mContactsPrefs.unregisterChangeListener();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * This is only effective for elements provided by {@link #mContactTileAdapter}.
+     * {@link #mContactTileAdapter} has its own logic for click events.
+     */
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        if (position <= contactTileAdapterCount) {
+            Log.e(TAG, "onItemClick() event for unexpected position. "
+                    + "The position " + position + " is before \"all\" section. Ignored.");
+        } else {
+            final int localPosition = position - mContactTileAdapter.getCount() - 1;
+            if (mListener != null) {
+                mListener.onContactSelected(mAllContactsAdapter.getDataUri(localPosition));
+            }
+        }
+    }
+
+    private boolean loadContactsPreferences() {
+        if (mContactsPrefs == null || mAllContactsAdapter == null) {
+            return false;
+        }
+
+        boolean changed = false;
+        if (mAllContactsAdapter.getContactNameDisplayOrder() != mContactsPrefs.getDisplayOrder()) {
+            mAllContactsAdapter.setContactNameDisplayOrder(mContactsPrefs.getDisplayOrder());
+            changed = true;
+        }
+
+        if (mAllContactsAdapter.getSortOrder() != mContactsPrefs.getSortOrder()) {
+            mAllContactsAdapter.setSortOrder(mContactsPrefs.getSortOrder());
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    /**
+     * Requests to reload "all" contacts. If the section is already loaded, this method will
+     * force reloading it now. If the section isn't loaded yet, the actual load may be done later
+     * (on {@link #onStart()}.
+     */
+    private void requestReloadAllContacts() {
+        if (DEBUG) {
+            Log.d(TAG, "requestReloadAllContacts()"
+                    + " mAllContactsAdapter: " + mAllContactsAdapter
+                    + ", mAllContactsLoaderStarted: " + mAllContactsLoaderStarted);
+        }
+
+        if (mAllContactsAdapter == null || !mAllContactsLoaderStarted) {
+            // Remember this request until next load on onStart().
+            mAllContactsForceReload = true;
+            return;
+        }
+
+        if (DEBUG) Log.d(TAG, "Reload \"all\" contacts now.");
+
+        mAllContactsAdapter.onDataReload();
+        // Use restartLoader() to make LoaderManager to load the section again.
+        getLoaderManager().restartLoader(LOADER_ID_ALL_CONTACTS, null, mAllContactsLoaderListener);
+    }
+
+    private void updateFilterHeaderView() {
+        if (mAccountFilterHeaderContainer == null || mAllContactsAdapter == null) {
+            return;
+        }
+
+        final ContactListFilter filter = getFilter();
+        if (mAllContactsAdapter.isLoading()) {
+            mAccountFilterHeaderView.setText(R.string.contact_list_loading);
+        } else if (filter != null) {
+            if (filter.filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) {
+                mAccountFilterHeaderView.setText(R.string.list_filter_phones);
+            } else if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) {
+                mAccountFilterHeaderView.setText(getString(
+                        R.string.listAllContactsInAccount, filter.accountName));
+            } else if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
+                mAccountFilterHeaderView.setText(R.string.listCustomView);
+            } else {
+                Log.w(TAG, "Filter type \"" + filter.filterType + "\" isn't expected.");
+            }
+        } else {
+            Log.w(TAG, "Filter is null.");
+        }
+    }
+
+    public ContactListFilter getFilter() {
+        return mFilter;
+    }
+
+    public void setFilter(ContactListFilter filter) {
+        if ((mFilter == null && filter == null) || (mFilter != null && mFilter.equals(filter))) {
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "setFilter(). old filter (" + mFilter
+                    + ") will be replaced with new filter (" + filter + ")");
+        }
+
+        mFilter = filter;
+        if (mPrefs != null) {
+            // Save the preference now.
+            ContactListFilter.storeToPreferences(mPrefs, mFilter);
+        }
+
+        mAllContactsAdapter.setFilter(mFilter);
+        requestReloadAllContacts();
+        updateFilterHeaderView();
+    }
+
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java b/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
new file mode 100644
index 0000000..c3c96ec
--- /dev/null
+++ b/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ * Licensed to 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.list;
+
+import com.android.contacts.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.SectionIndexer;
+
+/**
+ * An adapter that combines items from {@link ContactTileAdapter} and
+ * {@link ContactEntryListAdapter} into a single list. In between those two results,
+ * an account filter header will be inserted.
+ */
+public class PhoneFavoriteMergedAdapter extends BaseAdapter implements SectionIndexer {
+
+    // Should show nothing as SectionIndex.
+    // " " will suppress the section indexer itself: nothing will be shown during user's scroll.
+    private static final String SECTION_STRING_STARRED = " ";
+    private static final String SECTION_STRING_FREQUENT = " ";
+
+    private class CustomDataSetObserver extends DataSetObserver {
+        @Override
+        public void onChanged() {
+            notifyDataSetChanged();
+        }
+    }
+
+    private final ContactTileAdapter mContactTileAdapter;
+    private final ContactEntryListAdapter mContactEntryListAdapter;
+    private final View mAccountFilterHeaderContainer;
+
+    private final int mItemPaddingLeft;
+    private final int mItemPaddingRight;
+
+    private final DataSetObserver mObserver;
+
+    public PhoneFavoriteMergedAdapter(Context context,
+            ContactTileAdapter contactTileAdapter,
+            View accountFilterHeaderContainer,
+            ContactEntryListAdapter contactEntryListAdapter) {
+        Resources resources = context.getResources();
+        mItemPaddingLeft = resources.getDimensionPixelSize(R.dimen.detail_item_side_margin);
+        mItemPaddingRight = resources.getDimensionPixelSize(R.dimen.list_visible_scrollbar_padding);
+        mContactTileAdapter = contactTileAdapter;
+        mContactEntryListAdapter = contactEntryListAdapter;
+
+        mAccountFilterHeaderContainer = accountFilterHeaderContainer;
+
+        mObserver = new CustomDataSetObserver();
+        mContactTileAdapter.registerDataSetObserver(mObserver);
+        mContactEntryListAdapter.registerDataSetObserver(mObserver);
+    }
+
+    @Override
+    public int getCount() {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+        if (mContactEntryListAdapter.isLoading()) {
+            // Hide "all" contacts during its being loaded.
+            return contactTileAdapterCount + 1;
+        } else {
+            return contactTileAdapterCount + contactEntryListAdapterCount + 1;
+        }
+    }
+
+    @Override
+    public Object getItem(int position) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+        if (position < contactTileAdapterCount) {
+            return mContactTileAdapter.getItem(position);
+        } else if (position == contactTileAdapterCount) {
+            return mAccountFilterHeaderContainer;
+        } else {
+            final int localPosition = position - contactTileAdapterCount - 1;
+            return mContactTileAdapter.getItem(localPosition);
+        }
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return (mContactTileAdapter.getViewTypeCount()
+                + mContactEntryListAdapter.getViewTypeCount()
+                + 1);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+        if (position < contactTileAdapterCount) {
+            return mContactTileAdapter.getItemViewType(position);
+        } else if (position == contactTileAdapterCount) {
+            return mContactTileAdapter.getViewTypeCount()
+                    + mContactEntryListAdapter.getViewTypeCount();
+        } else {
+            final int localPosition = position - contactTileAdapterCount - 1;
+            final int type = mContactEntryListAdapter.getItemViewType(localPosition);
+            // IGNORE_ITEM_VIEW_TYPE must be handled differently.
+            return (type < 0) ? type : type + mContactTileAdapter.getViewTypeCount();
+        }
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+
+        // Obtain a View relevant for that position, and adjust its horizontal padding. Each
+        // View has different implementation, so we use different way to control those padding.
+        if (position < contactTileAdapterCount) {
+            final View view = mContactTileAdapter.getView(position, convertView, parent);
+            final int frequentHeaderPosition = mContactTileAdapter.getFrequentHeaderPosition();
+            if (position < frequentHeaderPosition) {  // "starred" contacts
+                // No padding adjustment.
+            } else if (position == frequentHeaderPosition) {
+                view.setPadding(mItemPaddingLeft, view.getPaddingTop(),
+                        mItemPaddingRight, view.getPaddingBottom());
+            } else {
+                // Views for "frequent" contacts use FrameLayout's margins instead of padding.
+                final FrameLayout frameLayout = (FrameLayout) view;
+                final View child = frameLayout.getChildAt(0);
+                FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                        FrameLayout.LayoutParams.WRAP_CONTENT,
+                        FrameLayout.LayoutParams.WRAP_CONTENT);
+                params.setMargins(mItemPaddingLeft, 0, mItemPaddingRight, 0);
+                child.setLayoutParams(params);
+            }
+            return view;
+        } else if (position == contactTileAdapterCount) {
+            mAccountFilterHeaderContainer.setPadding(mItemPaddingLeft,
+                    mAccountFilterHeaderContainer.getPaddingTop(),
+                    mItemPaddingRight,
+                    mAccountFilterHeaderContainer.getPaddingBottom());
+            return mAccountFilterHeaderContainer;
+        } else {
+            final int localPosition = position - contactTileAdapterCount - 1;
+            final ContactListItemView itemView = (ContactListItemView)
+                    mContactEntryListAdapter.getView(localPosition, convertView, null);
+            // We cannot simply use setPadding() because of ContactListItemView's restriction.
+            // See comments for setExtraPaddingPadding().
+            itemView.setExtraPadding(mItemPaddingLeft, mItemPaddingRight);
+            itemView.setSelectionBoundsHorizontalMargin(mItemPaddingLeft, mItemPaddingRight);
+            return itemView;
+        }
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return (mContactTileAdapter.areAllItemsEnabled()
+                && mAccountFilterHeaderContainer.isEnabled()
+                && mContactEntryListAdapter.areAllItemsEnabled());
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+        if (position < contactTileAdapterCount) {
+            return mContactTileAdapter.isEnabled(position);
+        } else if (position == contactTileAdapterCount) {
+            return mAccountFilterHeaderContainer.isEnabled();
+        } else {
+            final int localPosition = position - contactTileAdapterCount + 1;
+            return mContactEntryListAdapter.isEnabled(localPosition);
+        }
+    }
+
+    @Override
+    public int getPositionForSection(int sectionIndex) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        if (sectionIndex == 0) {  // "starred" section
+            return 0;
+        } else if (sectionIndex == 1) {  // "frequent" section
+            return mContactTileAdapter.getFrequentHeaderPosition();
+        } else {
+            final int localSection = sectionIndex - 2;
+            final int localPosition = mContactEntryListAdapter.getPositionForSection(localSection);
+            return contactTileAdapterCount + 1 + localPosition;
+        }
+    }
+
+    @Override
+    public int getSectionForPosition(int position) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        if (position < contactTileAdapterCount) {
+            return 0;
+        } else if (position == contactTileAdapterCount) {
+            return 1;
+        } else {
+            final int localPosition = position - contactTileAdapterCount - 1;
+            final int localSection = mContactEntryListAdapter.getSectionForPosition(localPosition);
+            return localSection + 2;
+        }
+    }
+
+    @Override
+    public Object[] getSections() {
+        // Copy sections from "all" contacts, and add two additional sections for "starred", and
+        // "frequent". Those new sections should not show anything as an indexer, but should
+        // let users select those sections with a vertical scroll.
+        final Object[] contactEntrySections = mContactEntryListAdapter.getSections();
+        final Object[] ret = new Object[contactEntrySections.length + 2];
+        System.arraycopy(contactEntrySections, 0, ret, 2, contactEntrySections.length);
+
+        ret[0] = SECTION_STRING_STARRED;
+        ret[1] = SECTION_STRING_FREQUENT;
+        return ret;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/list/PhoneNumberListAdapter.java b/src/com/android/contacts/list/PhoneNumberListAdapter.java
index 5c12dd6..b16b10c 100644
--- a/src/com/android/contacts/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/PhoneNumberListAdapter.java
@@ -71,6 +71,8 @@
     private int mDisplayNameColumnIndex;
     private int mAlternativeDisplayNameColumnIndex;
 
+    private ContactListItemView.PhotoPosition mPhotoPosition;
+
     public PhoneNumberListAdapter(Context context) {
         super(context);
 
@@ -217,6 +219,7 @@
         final ContactListItemView view = new ContactListItemView(context, null);
         view.setUnknownNameText(mUnknownNameText);
         view.setQuickContactEnabled(isQuickContactEnabled());
+        view.setPhotoPosition(mPhotoPosition);
         return view;
     }
 
@@ -315,4 +318,12 @@
 
         getPhotoLoader().loadPhoto(view.getPhotoView(), photoId, false, false);
     }
+
+    public void setPhotoPosition(ContactListItemView.PhotoPosition photoPosition) {
+        mPhotoPosition = photoPosition;
+    }
+
+    public ContactListItemView.PhotoPosition getPhotoPosition() {
+        return mPhotoPosition;
+    }
 }