Merge "Import revised translations."
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-hdpi/sym_action_email_holo_light.png b/res/drawable-hdpi/sym_action_email_holo_light.png
deleted file mode 100644
index e1ff65f..0000000
--- a/res/drawable-hdpi/sym_action_email_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/sym_action_goto_website_holo_light.png b/res/drawable-hdpi/sym_action_goto_website_holo_light.png
deleted file mode 100644
index 4c5c614..0000000
--- a/res/drawable-hdpi/sym_action_goto_website_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/sym_action_show_map_holo_light.png b/res/drawable-hdpi/sym_action_show_map_holo_light.png
deleted file mode 100644
index 4c6ba98..0000000
--- a/res/drawable-hdpi/sym_action_show_map_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/sym_action_talk_holo_light.png b/res/drawable-hdpi/sym_action_talk_holo_light.png
deleted file mode 100644
index 62ad687..0000000
--- a/res/drawable-hdpi/sym_action_talk_holo_light.png
+++ /dev/null
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-mdpi/sym_action_email_holo_light.png b/res/drawable-mdpi/sym_action_email_holo_light.png
deleted file mode 100644
index 3160adb..0000000
--- a/res/drawable-mdpi/sym_action_email_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_goto_website_holo_light.png b/res/drawable-mdpi/sym_action_goto_website_holo_light.png
deleted file mode 100644
index 5b17bd2..0000000
--- a/res/drawable-mdpi/sym_action_goto_website_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_show_map_holo_light.png b/res/drawable-mdpi/sym_action_show_map_holo_light.png
deleted file mode 100644
index 059e821..0000000
--- a/res/drawable-mdpi/sym_action_show_map_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_talk_holo_light.png b/res/drawable-mdpi/sym_action_talk_holo_light.png
deleted file mode 100644
index ff444d8..0000000
--- a/res/drawable-mdpi/sym_action_talk_holo_light.png
+++ /dev/null
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/drawable-xhdpi/sym_action_email_holo_light.png b/res/drawable-xhdpi/sym_action_email_holo_light.png
deleted file mode 100644
index e64a7ae..0000000
--- a/res/drawable-xhdpi/sym_action_email_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/sym_action_goto_website_holo_light.png b/res/drawable-xhdpi/sym_action_goto_website_holo_light.png
deleted file mode 100644
index 48873f4..0000000
--- a/res/drawable-xhdpi/sym_action_goto_website_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/sym_action_show_map_holo_light.png b/res/drawable-xhdpi/sym_action_show_map_holo_light.png
deleted file mode 100644
index 1c948f6..0000000
--- a/res/drawable-xhdpi/sym_action_show_map_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/sym_action_talk_holo_light.png b/res/drawable-xhdpi/sym_action_talk_holo_light.png
deleted file mode 100644
index 759c7e8..0000000
--- a/res/drawable-xhdpi/sym_action_talk_holo_light.png
+++ /dev/null
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/custom_action_bar.xml b/res/layout/custom_action_bar.xml
index f749586..af104fe 100644
--- a/res/layout/custom_action_bar.xml
+++ b/res/layout/custom_action_bar.xml
@@ -32,6 +32,7 @@
         android:id="@+id/search_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:iconifiedByDefault="false" />
+        android:iconifiedByDefault="false"
+        android:inputType="textFilter" />
 
 </FrameLayout>
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/layout/dialtacts_custom_action_bar.xml b/res/layout/dialtacts_custom_action_bar.xml
index 0709626..0af8eaa 100644
--- a/res/layout/dialtacts_custom_action_bar.xml
+++ b/res/layout/dialtacts_custom_action_bar.xml
@@ -26,7 +26,8 @@
         android:layout_width="0px"
         android:layout_height="match_parent"
         android:layout_weight="1"
-        android:iconifiedByDefault="false" />
+        android:iconifiedByDefault="false"
+        android:inputType="textFilter" />
 
     <ImageButton
         android:id="@+id/search_option"
diff --git a/res/layout/editor_custom_action_bar.xml b/res/layout/editor_custom_action_bar.xml
index c22b089..1040da3 100644
--- a/res/layout/editor_custom_action_bar.xml
+++ b/res/layout/editor_custom_action_bar.xml
@@ -26,13 +26,14 @@
         android:divider="?android:attr/dividerVertical"
         android:showDividers="end"
         android:dividerPadding="12dip"
-        android:orientation="horizontal"
-        style="?android:attr/actionButtonStyle">
+        android:orientation="horizontal">
 
         <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:orientation="horizontal">
+            android:orientation="horizontal"
+            android:duplicateParentState="true"
+            style="?android:attr/actionButtonStyle">
 
             <ImageView
                 android:id="@+id/icon"
diff --git a/res/layout/quickcontact_photo_container.xml b/res/layout/quickcontact_photo_container.xml
index 1ba939a..e970934 100644
--- a/res/layout/quickcontact_photo_container.xml
+++ b/res/layout/quickcontact_photo_container.xml
@@ -61,30 +61,6 @@
             android:ellipsize="end"
             android:textColor="@android:color/white"
             android:textAppearance="?android:attr/textAppearanceMedium" />
-        <TextView
-            android:id="@+id/status"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:textColor="@*android:color/secondary_text_light"
-            android:textSize="15dip"
-            android:layout_marginTop="-3dip" />
-        <TextView
-            android:id="@+id/timestamp"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:textColor="@*android:color/secondary_text_light"
-            android:textSize="12dip"
-            android:layout_marginTop="-2dip" />
-        <ImageView
-            android:id="@+id/presence"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginRight="15dip"
-            android:scaleType="centerInside" />
         <ImageButton
             android:id="@+id/open_details_push_layer"
             android:layout_width="match_parent"
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/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 113a18b..012acc7 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -19,6 +19,8 @@
 import com.android.contacts.BackScrollManager.ScrollableHeader;
 import com.android.contacts.calllog.CallDetailHistoryAdapter;
 import com.android.contacts.calllog.CallTypeHelper;
+import com.android.contacts.calllog.ContactInfo;
+import com.android.contacts.calllog.ContactInfoHelper;
 import com.android.contacts.calllog.PhoneNumberHelper;
 import com.android.contacts.util.AsyncTaskExecutor;
 import com.android.contacts.util.AsyncTaskExecutors;
@@ -44,7 +46,6 @@
 import android.provider.Contacts.Intents.Insert;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.PhoneLookup;
 import android.provider.VoicemailContract.Voicemails;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
@@ -101,6 +102,7 @@
     private ImageButton mMainActionPushLayerView;
     private ImageView mContactBackgroundView;
     private AsyncTaskExecutor mAsyncTaskExecutor;
+    private ContactInfoHelper mContactInfoHelper;
 
     private String mNumber = null;
     private String mDefaultCountryIso;
@@ -191,25 +193,6 @@
     static final int COUNTRY_ISO_COLUMN_INDEX = 4;
     static final int GEOCODED_LOCATION_COLUMN_INDEX = 5;
 
-    static final String[] PHONES_PROJECTION = new String[] {
-        PhoneLookup._ID,
-        PhoneLookup.DISPLAY_NAME,
-        PhoneLookup.TYPE,
-        PhoneLookup.LABEL,
-        PhoneLookup.NUMBER,
-        PhoneLookup.NORMALIZED_NUMBER,
-        PhoneLookup.PHOTO_URI,
-        PhoneLookup.LOOKUP_KEY,
-    };
-    static final int COLUMN_INDEX_ID = 0;
-    static final int COLUMN_INDEX_NAME = 1;
-    static final int COLUMN_INDEX_TYPE = 2;
-    static final int COLUMN_INDEX_LABEL = 3;
-    static final int COLUMN_INDEX_NUMBER = 4;
-    static final int COLUMN_INDEX_NORMALIZED_NUMBER = 5;
-    static final int COLUMN_INDEX_PHOTO_URI = 6;
-    static final int COLUMN_INDEX_LOOKUP_KEY = 7;
-
     private final View.OnClickListener mPrimaryActionListener = new View.OnClickListener() {
         @Override
         public void onClick(View view) {
@@ -251,6 +234,7 @@
         mDefaultCountryIso = ContactsUtils.getCurrentCountryIso(this);
         mContactPhotoManager = ContactPhotoManager.getInstance(this);
         mProximitySensorManager = new ProximitySensorManager(this, mProximitySensorListener);
+        mContactInfoHelper = new ContactInfoHelper(this, ContactsUtils.getCurrentCountryIso(this));
         configureActionBar();
         optionallyHandleVoicemail();
     }
@@ -570,53 +554,36 @@
             }
 
             // Formatted phone number.
-            final CharSequence numberText;
+            final CharSequence formattedNumber;
             // Read contact specifics.
-            CharSequence nameText = "";
-            int numberType = 0;
-            CharSequence numberLabel = "";
-            Uri photoUri = null;
-            Uri contactUri = null;
+            final CharSequence nameText;
+            final int numberType;
+            final CharSequence numberLabel;
+            final Uri photoUri;
+            final Uri lookupUri;
             // If this is not a regular number, there is no point in looking it up in the contacts.
-            if (!mPhoneNumberHelper.canPlaceCallsTo(number)) {
-                numberText = mPhoneNumberHelper.getDisplayNumber(number, null);
+            ContactInfo info =
+                    mPhoneNumberHelper.canPlaceCallsTo(number)
+                            ? mContactInfoHelper.lookupNumber(number, countryIso)
+                            : null;
+            if (info == null) {
+                formattedNumber = mPhoneNumberHelper.getDisplayNumber(number, null);
+                nameText = "";
+                numberType = 0;
+                numberLabel = "";
+                photoUri = null;
+                lookupUri = null;
             } else {
-                // Perform a reverse-phonebook lookup to find the contact details.
-                Uri phoneUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
-                        Uri.encode(number));
-                Cursor phonesCursor = resolver.query(phoneUri, PHONES_PROJECTION, null, null, null);
-                String candidateNumberText = number;
-                try {
-                    if (phonesCursor != null && phonesCursor.moveToFirst()) {
-                        nameText = phonesCursor.getString(COLUMN_INDEX_NAME);
-                        String photoUriString = phonesCursor.getString(COLUMN_INDEX_PHOTO_URI);
-                        photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
-                        candidateNumberText = PhoneNumberUtils.formatNumber(
-                                phonesCursor.getString(COLUMN_INDEX_NUMBER),
-                                phonesCursor.getString(COLUMN_INDEX_NORMALIZED_NUMBER),
-                                countryIso);
-                        numberType = phonesCursor.getInt(COLUMN_INDEX_TYPE);
-                        numberLabel = phonesCursor.getString(COLUMN_INDEX_LABEL);
-                        long personId = phonesCursor.getLong(COLUMN_INDEX_ID);
-                        if (personId > 0) {
-                            contactUri = Contacts.getLookupUri(personId,
-                                    phonesCursor.getString(COLUMN_INDEX_LOOKUP_KEY));
-                        }
-                    } else {
-                        // We could not find this contact in the contacts, just format the phone
-                        // number as best as we can. All the other fields will have their default
-                        // values.
-                        candidateNumberText =
-                                PhoneNumberUtils.formatNumber(number, countryIso);
-                    }
-                } finally {
-                    if (phonesCursor != null) phonesCursor.close();
-                    numberText = candidateNumberText;
-                }
+                formattedNumber = info.formattedNumber;
+                nameText = info.name;
+                numberType = info.type;
+                numberLabel = info.label;
+                photoUri = info.photoUri;
+                lookupUri = info.lookupUri;
             }
-            return new PhoneCallDetails(number, numberText, countryIso, geocode,
+            return new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
                     new int[]{ callType }, date, duration,
-                    nameText, numberType, numberLabel, contactUri, photoUri);
+                    nameText, numberType, numberLabel, lookupUri, photoUri);
         } finally {
             if (callCursor != null) {
                 callCursor.close();
diff --git a/src/com/android/contacts/ContactPresenceIconUtil.java b/src/com/android/contacts/ContactPresenceIconUtil.java
index 8e1af72..0cb5b93 100644
--- a/src/com/android/contacts/ContactPresenceIconUtil.java
+++ b/src/com/android/contacts/ContactPresenceIconUtil.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.StatusUpdates;
 
 /**
@@ -46,43 +45,4 @@
                 return null;
         }
     }
-
-    public static Drawable getChatCapabilityIcon(Context context, int status, int chatCapability) {
-        int resourceId = 0;
-        if ((chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0) {
-            switch(status) {
-                case StatusUpdates.AVAILABLE:
-                    resourceId = android.R.drawable.presence_video_online;
-                    break;
-                case StatusUpdates.IDLE:
-                case StatusUpdates.AWAY:
-                    resourceId = android.R.drawable.presence_video_away;
-                    break;
-                case StatusUpdates.DO_NOT_DISTURB:
-                    resourceId = android.R.drawable.presence_video_busy;
-                    break;
-            }
-        } else if ((chatCapability & Im.CAPABILITY_HAS_VOICE) != 0) {
-            switch(status) {
-                case StatusUpdates.AVAILABLE:
-                    resourceId = android.R.drawable.presence_audio_online;
-                    break;
-                case StatusUpdates.IDLE:
-                case StatusUpdates.AWAY:
-                    resourceId = android.R.drawable.presence_audio_away;
-                    break;
-                case StatusUpdates.DO_NOT_DISTURB:
-                    resourceId = android.R.drawable.presence_audio_busy;
-                    break;
-            }
-        } else if (status != StatusUpdates.OFFLINE) {
-            resourceId = StatusUpdates.getPresenceIconResourceId(status);
-        }
-
-        if (resourceId != 0) {
-            return context.getResources().getDrawable(resourceId);
-        }
-
-        return null;
-    }
 }
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/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index b088b89..0c3f448 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -111,8 +111,6 @@
     private static final int SUBACTIVITY_NEW_GROUP = 2;
     private static final int SUBACTIVITY_EDIT_GROUP = 3;
 
-    private static final String KEY_SEARCH_MODE = "searchMode";
-
     private DialogManager mDialogManager = new DialogManager(this);
 
     private ContactsIntentResolver mIntentResolver;
diff --git a/src/com/android/contacts/calllog/CallLogAdapter.java b/src/com/android/contacts/calllog/CallLogAdapter.java
index 17209b4..4f274a9 100644
--- a/src/com/android/contacts/calllog/CallLogAdapter.java
+++ b/src/com/android/contacts/calllog/CallLogAdapter.java
@@ -33,11 +33,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.CommonDataKinds.SipAddress;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.PhoneLookup;
-import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -65,7 +61,7 @@
     private static final int CONTACT_INFO_CACHE_SIZE = 100;
 
     private final Context mContext;
-    private final String mCurrentCountryIso;
+    private final ContactInfoHelper mContactInfoHelper;
     private final CallFetcher mCallFetcher;
 
     /**
@@ -198,12 +194,12 @@
     };
 
     public CallLogAdapter(Context context, CallFetcher callFetcher,
-            String currentCountryIso, String voicemailNumber) {
+            ContactInfoHelper contactInfoHelper, String voicemailNumber) {
         super(context);
 
         mContext = context;
-        mCurrentCountryIso = currentCountryIso;
         mCallFetcher = callFetcher;
+        mContactInfoHelper = contactInfoHelper;
 
         mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
         mRequests = new LinkedList<ContactInfoRequest>();
@@ -305,134 +301,6 @@
     }
 
     /**
-     * Determines the contact information for the given SIP address.
-     * <p>
-     * It returns the contact info if found.
-     * <p>
-     * If no contact corresponds to the given SIP address, returns {@link ContactInfo#EMPTY}.
-     * <p>
-     * If the lookup fails for some other reason, it returns null.
-     */
-    private ContactInfo queryContactInfoForSipAddress(String sipAddress) {
-        final ContactInfo info;
-
-        // TODO: This code is duplicated from the
-        // CallerInfoAsyncQuery class.  To avoid that, could the
-        // code here just use CallerInfoAsyncQuery, rather than
-        // manually running ContentResolver.query() itself?
-
-        // We look up SIP addresses directly in the Data table:
-        Uri contactRef = Data.CONTENT_URI;
-
-        // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
-        //
-        // Also note we use "upper(data1)" in the WHERE clause, and
-        // uppercase the incoming SIP address, in order to do a
-        // case-insensitive match.
-        //
-        // TODO: May also need to normalize by adding "sip:" as a
-        // prefix, if we start storing SIP addresses that way in the
-        // database.
-        String selection = "upper(" + Data.DATA1 + ")=?"
-                + " AND "
-                + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
-        String[] selectionArgs = new String[] { sipAddress.toUpperCase() };
-
-        Cursor dataTableCursor =
-                mContext.getContentResolver().query(
-                        contactRef,
-                        null,  // projection
-                        selection,  // selection
-                        selectionArgs,  // selectionArgs
-                        null);  // sortOrder
-
-        if (dataTableCursor != null) {
-            if (dataTableCursor.moveToFirst()) {
-                info = new ContactInfo();
-
-                // TODO: we could slightly speed this up using an
-                // explicit projection (and thus not have to do
-                // those getColumnIndex() calls) but the benefit is
-                // very minimal.
-
-                // Note the Data.CONTACT_ID column here is
-                // equivalent to the PERSON_ID_COLUMN_INDEX column
-                // we use with "phonesCursor" below.
-                long contactId = dataTableCursor.getLong(
-                        dataTableCursor.getColumnIndex(Data.CONTACT_ID));
-                String lookupKey = dataTableCursor.getString(
-                        dataTableCursor.getColumnIndex(Data.LOOKUP_KEY));
-                info.lookupUri = Contacts.getLookupUri(contactId, lookupKey);
-                info.name = dataTableCursor.getString(
-                        dataTableCursor.getColumnIndex(Data.DISPLAY_NAME));
-                // "type" and "label" are currently unused for SIP addresses
-                info.type = SipAddress.TYPE_OTHER;
-                info.label = null;
-
-                // And "number" is the SIP address.
-                // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
-                info.number = dataTableCursor.getString(dataTableCursor.getColumnIndex(Data.DATA1));
-                info.normalizedNumber = null;  // meaningless for SIP addresses
-                info.photoId = dataTableCursor.getLong(
-                        dataTableCursor.getColumnIndex(Data.PHOTO_ID));
-                info.formattedNumber = null;  // meaningless for SIP addresses
-            } else {
-                info = ContactInfo.EMPTY;
-            }
-            dataTableCursor.close();
-        } else {
-            // Failed to fetch the data, ignore this request.
-            info = null;
-        }
-        return info;
-    }
-
-    /**
-     * Determines the contact information for the given phone number.
-     * <p>
-     * It returns the contact info if found.
-     * <p>
-     * If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
-     * <p>
-     * If the lookup fails for some other reason, it returns null.
-     */
-    private ContactInfo queryContactInfoForPhoneNumber(String number, String countryIso) {
-        final ContactInfo info;
-
-        // "number" is a regular phone number, so use the
-        // PhoneLookup table:
-        Cursor phonesCursor =
-                mContext.getContentResolver().query(
-                    Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
-                            Uri.encode(number)),
-                            PhoneQuery._PROJECTION, null, null, null);
-        if (phonesCursor != null) {
-            if (phonesCursor.moveToFirst()) {
-                info = new ContactInfo();
-                long contactId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
-                String lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
-                info.lookupUri = Contacts.getLookupUri(contactId, lookupKey);
-                info.name = phonesCursor.getString(PhoneQuery.NAME);
-                info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
-                info.label = phonesCursor.getString(PhoneQuery.LABEL);
-                info.number = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
-                info.normalizedNumber = phonesCursor.getString(PhoneQuery.NORMALIZED_NUMBER);
-                info.photoId = phonesCursor.getLong(PhoneQuery.PHOTO_ID);
-                info.formattedNumber = formatPhoneNumber(info.number, info.formattedNumber,
-                        countryIso);
-
-            } else {
-                info = ContactInfo.EMPTY;
-            }
-            phonesCursor.close();
-        } else {
-            // Failed to fetch the data, ignore this request.
-            info = null;
-        }
-        return info;
-    }
-
-    /**
      * Queries the appropriate content provider for the contact associated with the number.
      * <p>
      * Upon completion it also updates the cache in the call log, if it is different from
@@ -444,53 +312,25 @@
      * view to update its content.
      */
     private boolean queryContactInfo(String number, String countryIso, ContactInfo callLogInfo) {
-        final ContactInfo info;
-
-        // Determine the contact info.
-        if (PhoneNumberUtils.isUriNumber(number)) {
-            // This "number" is really a SIP address.
-            ContactInfo sipInfo = queryContactInfoForSipAddress(number);
-            if (sipInfo == null || sipInfo == ContactInfo.EMPTY) {
-                // Check whether the username is actually a phone number of contact.
-                String username = number.substring(0, number.indexOf('@'));
-                if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
-                    sipInfo = queryContactInfoForPhoneNumber(username, countryIso);
-                }
-            }
-            info = sipInfo;
-        } else {
-            info = queryContactInfoForPhoneNumber(number, countryIso);
-        }
+        final ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso);
 
         if (info == null) {
             // The lookup failed, just return without requesting to update the view.
             return false;
         }
 
-        final ContactInfo updatedInfo;
-        // If we did not find a matching contact, generate an empty contact info for the number.
-        if (info == ContactInfo.EMPTY) {
-            // Did not find a matching contact.
-            updatedInfo = new ContactInfo();
-            updatedInfo.number = number;
-            updatedInfo.formattedNumber = formatPhoneNumber(number, null, countryIso);
-        } else {
-            updatedInfo = info;
-        }
-
         // Check the existing entry in the cache: only if it has changed we should update the
         // view.
         ContactInfo existingInfo = mContactInfoCache.getPossiblyExpired(number);
-        boolean updated = !updatedInfo.equals(existingInfo);
+        boolean updated = !info.equals(existingInfo);
         // Store the data in the cache so that the UI thread can use to display it. Store it
         // even if it has not changed so that it is marked as not expired.
-        mContactInfoCache.put(number, updatedInfo);
+        mContactInfoCache.put(number, info);
         // Update the call log even if the cache it is up-to-date: it is possible that the cache
         // contains the value from a different call log entry.
-        updateCallLogContactInfoCache(number, updatedInfo, callLogInfo);
+        updateCallLogContactInfoCache(number, info, callLogInfo);
         return updated;
     }
-
     /*
      * Handles requests for contact name and number type
      * @see java.lang.Runnable#run()
@@ -804,6 +644,7 @@
         info.number = matchedNumber == null ? c.getString(CallLogQuery.NUMBER) : matchedNumber;
         info.normalizedNumber = c.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER);
         info.photoId = c.getLong(CallLogQuery.CACHED_PHOTO_ID);
+        info.photoUri = null;  // We do not cache the photo URI.
         info.formattedNumber = c.getString(CallLogQuery.CACHED_FORMATTED_NUMBER);
         return info;
     }
@@ -850,32 +691,6 @@
         super.addGroup(cursorPosition, size, expanded);
     }
 
-    /**
-     * Format the given phone number
-     *
-     * @param number the number to be formatted.
-     * @param normalizedNumber the normalized number of the given number.
-     * @param countryIso the ISO 3166-1 two letters country code, the country's
-     *        convention will be used to format the number if the normalized
-     *        phone is null.
-     *
-     * @return the formatted number, or the given number if it was formatted.
-     */
-    private String formatPhoneNumber(String number, String normalizedNumber,
-            String countryIso) {
-        if (TextUtils.isEmpty(number)) {
-            return "";
-        }
-        // If "number" is really a SIP address, don't try to do any formatting at all.
-        if (PhoneNumberUtils.isUriNumber(number)) {
-            return number;
-        }
-        if (TextUtils.isEmpty(countryIso)) {
-            countryIso = mCurrentCountryIso;
-        }
-        return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
-    }
-
     /*
      * Get the number from the Contacts, if available, since sometimes
      * the number provided by caller id may not be formatted properly
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index c9a4b5b..f66bbd0 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -152,7 +152,8 @@
     public void onViewCreated(View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         String currentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
-        mAdapter = new CallLogAdapter(getActivity(), this, currentCountryIso, mVoiceMailNumber);
+        mAdapter = new CallLogAdapter(getActivity(), this,
+                new ContactInfoHelper(getActivity(), currentCountryIso), mVoiceMailNumber);
         setListAdapter(mAdapter);
         getListView().setItemsCanFocus(true);
     }
diff --git a/src/com/android/contacts/calllog/ContactInfo.java b/src/com/android/contacts/calllog/ContactInfo.java
index cf33d45..a02b457 100644
--- a/src/com/android/contacts/calllog/ContactInfo.java
+++ b/src/com/android/contacts/calllog/ContactInfo.java
@@ -34,6 +34,8 @@
     public String normalizedNumber;
     /** The photo for the contact, if available. */
     public long photoId;
+    /** The high-res photo for the contact, if available. */
+    public Uri photoUri;
 
     public static ContactInfo EMPTY = new ContactInfo();
 
@@ -63,6 +65,7 @@
         if (!TextUtils.equals(formattedNumber, other.formattedNumber)) return false;
         if (!TextUtils.equals(normalizedNumber, other.normalizedNumber)) return false;
         if (photoId != other.photoId) return false;
+        if (!UriUtils.areEqual(photoUri, other.photoUri)) return false;
         return true;
     }
 }
diff --git a/src/com/android/contacts/calllog/ContactInfoHelper.java b/src/com/android/contacts/calllog/ContactInfoHelper.java
new file mode 100644
index 0000000..f4b7daf
--- /dev/null
+++ b/src/com/android/contacts/calllog/ContactInfoHelper.java
@@ -0,0 +1,248 @@
+/*
+ * 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.contacts.util.UriUtils;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.PhoneLookup;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+
+/**
+ * Utility class to look up the contact information for a given number.
+ */
+public class ContactInfoHelper {
+    private final Context mContext;
+    private final String mCurrentCountryIso;
+
+    public ContactInfoHelper(Context context, String currentCountryIso) {
+        mContext = context;
+        mCurrentCountryIso = currentCountryIso;
+    }
+
+    /**
+     * Returns the contact information for the given number.
+     * <p>
+     * If the number does not match any contact, returns a contact info containing only the number
+     * and the formatted number.
+     * <p>
+     * If an error occurs during the lookup, it returns null.
+     *
+     * @param number the number to look up
+     * @param countryIso the country associated with this number
+     */
+    public ContactInfo lookupNumber(String number, String countryIso) {
+        final ContactInfo info;
+
+        // Determine the contact info.
+        if (PhoneNumberUtils.isUriNumber(number)) {
+            // This "number" is really a SIP address.
+            ContactInfo sipInfo = queryContactInfoForSipAddress(number);
+            if (sipInfo == null || sipInfo == ContactInfo.EMPTY) {
+                // Check whether the username is actually a phone number of contact.
+                String username = number.substring(0, number.indexOf('@'));
+                if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
+                    sipInfo = queryContactInfoForPhoneNumber(username, countryIso);
+                }
+            }
+            info = sipInfo;
+        } else {
+            info = queryContactInfoForPhoneNumber(number, countryIso);
+        }
+
+        final ContactInfo updatedInfo;
+        if (info == null) {
+            // The lookup failed.
+            updatedInfo = null;
+        } else {
+            // If we did not find a matching contact, generate an empty contact info for the number.
+            if (info == ContactInfo.EMPTY) {
+                // Did not find a matching contact.
+                updatedInfo = new ContactInfo();
+                updatedInfo.number = number;
+                updatedInfo.formattedNumber = formatPhoneNumber(number, null, countryIso);
+            } else {
+                updatedInfo = info;
+            }
+        }
+        return updatedInfo;
+    }
+
+    /**
+     * Determines the contact information for the given SIP address.
+     * <p>
+     * It returns the contact info if found.
+     * <p>
+     * If no contact corresponds to the given SIP address, returns {@link ContactInfo#EMPTY}.
+     * <p>
+     * If the lookup fails for some other reason, it returns null.
+     */
+    private ContactInfo queryContactInfoForSipAddress(String sipAddress) {
+        final ContactInfo info;
+
+        // TODO: This code is duplicated from the
+        // CallerInfoAsyncQuery class.  To avoid that, could the
+        // code here just use CallerInfoAsyncQuery, rather than
+        // manually running ContentResolver.query() itself?
+
+        // We look up SIP addresses directly in the Data table:
+        Uri contactRef = Data.CONTENT_URI;
+
+        // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+        //
+        // Also note we use "upper(data1)" in the WHERE clause, and
+        // uppercase the incoming SIP address, in order to do a
+        // case-insensitive match.
+        //
+        // TODO: May also need to normalize by adding "sip:" as a
+        // prefix, if we start storing SIP addresses that way in the
+        // database.
+        String selection = "upper(" + Data.DATA1 + ")=?"
+                + " AND "
+                + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
+        String[] selectionArgs = new String[] { sipAddress.toUpperCase() };
+
+        Cursor dataTableCursor =
+                mContext.getContentResolver().query(
+                        contactRef,
+                        null,  // projection
+                        selection,  // selection
+                        selectionArgs,  // selectionArgs
+                        null);  // sortOrder
+
+        if (dataTableCursor != null) {
+            if (dataTableCursor.moveToFirst()) {
+                info = new ContactInfo();
+
+                // TODO: we could slightly speed this up using an
+                // explicit projection (and thus not have to do
+                // those getColumnIndex() calls) but the benefit is
+                // very minimal.
+
+                // Note the Data.CONTACT_ID column here is
+                // equivalent to the PERSON_ID_COLUMN_INDEX column
+                // we use with "phonesCursor" below.
+                long contactId = dataTableCursor.getLong(
+                        dataTableCursor.getColumnIndex(Data.CONTACT_ID));
+                String lookupKey = dataTableCursor.getString(
+                        dataTableCursor.getColumnIndex(Data.LOOKUP_KEY));
+                info.lookupUri = Contacts.getLookupUri(contactId, lookupKey);
+                info.name = dataTableCursor.getString(
+                        dataTableCursor.getColumnIndex(Data.DISPLAY_NAME));
+                // "type" and "label" are currently unused for SIP addresses
+                info.type = SipAddress.TYPE_OTHER;
+                info.label = null;
+
+                // And "number" is the SIP address.
+                // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+                info.number = dataTableCursor.getString(dataTableCursor.getColumnIndex(Data.DATA1));
+                info.normalizedNumber = null;  // meaningless for SIP addresses
+                info.photoId = dataTableCursor.getLong(
+                        dataTableCursor.getColumnIndex(Data.PHOTO_ID));
+                info.photoUri = UriUtils.parseUriOrNull(dataTableCursor.getString(
+                        dataTableCursor.getColumnIndex(Data.PHOTO_URI)));
+                info.formattedNumber = null;  // meaningless for SIP addresses
+            } else {
+                info = ContactInfo.EMPTY;
+            }
+            dataTableCursor.close();
+        } else {
+            // Failed to fetch the data, ignore this request.
+            info = null;
+        }
+        return info;
+    }
+
+    /**
+     * Determines the contact information for the given phone number.
+     * <p>
+     * It returns the contact info if found.
+     * <p>
+     * If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
+     * <p>
+     * If the lookup fails for some other reason, it returns null.
+     */
+    private ContactInfo queryContactInfoForPhoneNumber(String number, String countryIso) {
+        final ContactInfo info;
+
+        // "number" is a regular phone number, so use the
+        // PhoneLookup table:
+        Cursor phonesCursor =
+                mContext.getContentResolver().query(
+                    Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+                            Uri.encode(number)),
+                            PhoneQuery._PROJECTION, null, null, null);
+        if (phonesCursor != null) {
+            if (phonesCursor.moveToFirst()) {
+                info = new ContactInfo();
+                long contactId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
+                String lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
+                info.lookupUri = Contacts.getLookupUri(contactId, lookupKey);
+                info.name = phonesCursor.getString(PhoneQuery.NAME);
+                info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
+                info.label = phonesCursor.getString(PhoneQuery.LABEL);
+                info.number = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
+                info.normalizedNumber = phonesCursor.getString(PhoneQuery.NORMALIZED_NUMBER);
+                info.photoId = phonesCursor.getLong(PhoneQuery.PHOTO_ID);
+                info.photoUri =
+                        UriUtils.parseUriOrNull(phonesCursor.getString(PhoneQuery.PHOTO_URI));
+                info.formattedNumber = formatPhoneNumber(info.number, info.formattedNumber,
+                        countryIso);
+
+            } else {
+                info = ContactInfo.EMPTY;
+            }
+            phonesCursor.close();
+        } else {
+            // Failed to fetch the data, ignore this request.
+            info = null;
+        }
+        return info;
+    }
+
+    /**
+     * Format the given phone number
+     *
+     * @param number the number to be formatted.
+     * @param normalizedNumber the normalized number of the given number.
+     * @param countryIso the ISO 3166-1 two letters country code, the country's
+     *        convention will be used to format the number if the normalized
+     *        phone is null.
+     *
+     * @return the formatted number, or the given number if it was formatted.
+     */
+    private String formatPhoneNumber(String number, String normalizedNumber,
+            String countryIso) {
+        if (TextUtils.isEmpty(number)) {
+            return "";
+        }
+        // If "number" is really a SIP address, don't try to do any formatting at all.
+        if (PhoneNumberUtils.isUriNumber(number)) {
+            return number;
+        }
+        if (TextUtils.isEmpty(countryIso)) {
+            countryIso = mCurrentCountryIso;
+        }
+        return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
+    }
+}
diff --git a/src/com/android/contacts/calllog/PhoneQuery.java b/src/com/android/contacts/calllog/PhoneQuery.java
index a53e5c8..af44add 100644
--- a/src/com/android/contacts/calllog/PhoneQuery.java
+++ b/src/com/android/contacts/calllog/PhoneQuery.java
@@ -30,7 +30,8 @@
             PhoneLookup.NUMBER,
             PhoneLookup.NORMALIZED_NUMBER,
             PhoneLookup.PHOTO_ID,
-            PhoneLookup.LOOKUP_KEY};
+            PhoneLookup.LOOKUP_KEY,
+            PhoneLookup.PHOTO_URI};
 
     public static final int PERSON_ID = 0;
     public static final int NAME = 1;
@@ -40,4 +41,5 @@
     public static final int NORMALIZED_NUMBER = 5;
     public static final int PHOTO_ID = 6;
     public static final int LOOKUP_KEY = 7;
+    public static final int PHOTO_URI = 8;
 }
\ No newline at end of file
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index aaeacab..aa49481 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -593,19 +593,18 @@
                     final Intent smsIntent = mHasSms ? new Intent(Intent.ACTION_SENDTO,
                             Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null)) : null;
 
-                    // Configure Icons and Intents. Notice actionIcon is already set to the phone
+                    // Configure Icons and Intents.
                     if (mHasPhone && mHasSms) {
                         entry.intent = phoneIntent;
                         entry.secondaryIntent = smsIntent;
                         entry.secondaryActionIcon = kind.iconAltRes;
+                        entry.secondaryActionDescription = kind.iconAltDescriptionRes;
                     } else if (mHasPhone) {
                         entry.intent = phoneIntent;
                     } else if (mHasSms) {
                         entry.intent = smsIntent;
-                        entry.actionIcon = kind.iconAltRes;
                     } else {
                         entry.intent = null;
-                        entry.actionIcon = -1;
                     }
 
                     // Remember super-primary phone
@@ -690,7 +689,6 @@
                                 Uri.fromParts(Constants.SCHEME_SIP, entry.data, null));
                     } else {
                         entry.intent = null;
-                        entry.actionIcon = -1;
                     }
                     mSipEntries.add(entry);
                     // TODO: Now that SipAddress is in its own list of entries
@@ -983,20 +981,17 @@
             entry.typeString = Im.getProtocolLabel(context.getResources(), Im.PROTOCOL_GOOGLE_TALK,
                     null).toString();
             if ((chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0) {
-                entry.actionIcon = R.drawable.sym_action_talk_holo_light;
                 entry.intent =
                         new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message"));
                 entry.secondaryIntent =
                         new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call"));
             } else if ((chatCapability & Im.CAPABILITY_HAS_VOICE) != 0) {
                 // Allow Talking and Texting
-                entry.actionIcon = R.drawable.sym_action_talk_holo_light;
                 entry.intent =
                     new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message"));
                 entry.secondaryIntent =
                     new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call"));
             } else {
-                entry.actionIcon = R.drawable.sym_action_talk_holo_light;
                 entry.intent =
                     new Intent(Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?message"));
             }
@@ -1013,7 +1008,6 @@
                 final String authority = host.toLowerCase();
                 final Uri imUri = new Uri.Builder().scheme(Constants.SCHEME_IMTO).authority(
                         authority).appendPath(data).build();
-                entry.actionIcon = R.drawable.sym_action_talk_holo_light;
                 entry.intent = new Intent(Intent.ACTION_SENDTO, imUri);
             }
         }
@@ -1210,9 +1204,9 @@
 
         public Context context = null;
         public String resPackageName = null;
-        public int actionIcon = -1;
         public boolean isPrimary = false;
         public int secondaryActionIcon = -1;
+        public int secondaryActionDescription = -1;
         public Intent intent;
         public Intent secondaryIntent = null;
         public ArrayList<Long> ids = new ArrayList<Long>();
@@ -1247,6 +1241,7 @@
             entry.kind = (kind.titleRes == -1 || kind.titleRes == 0) ? ""
                     : context.getString(kind.titleRes);
             entry.data = buildDataString(kind, values, context);
+            entry.resPackageName = kind.resPackageName;
 
             if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) {
                 entry.type = values.getAsInteger(kind.typeColumn);
@@ -1269,11 +1264,6 @@
                 entry.typeString = "";
             }
 
-            if (kind.iconRes > 0) {
-                entry.resPackageName = kind.resPackageName;
-                entry.actionIcon = kind.iconRes;
-            }
-
             return entry;
         }
 
@@ -1349,8 +1339,8 @@
 
             if (!TextUtils.equals(mimetype, entry.mimetype)
                     || !ContactsUtils.areIntentActionEqual(intent, entry.intent)
-                    || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent)
-                    || actionIcon != entry.actionIcon) {
+                    || !ContactsUtils.areIntentActionEqual(
+                            secondaryIntent, entry.secondaryIntent)) {
                 return false;
             }
 
@@ -1633,19 +1623,24 @@
             // Set the secondary action button
             final ImageView secondaryActionView = views.secondaryActionButton;
             Drawable secondaryActionIcon = null;
+            String secondaryActionDescription = null;
             if (entry.secondaryActionIcon != -1) {
                 secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon);
+                secondaryActionDescription = resources.getString(entry.secondaryActionDescription);
             } else if ((entry.chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0) {
                 secondaryActionIcon =
                         resources.getDrawable(R.drawable.sym_action_videochat_holo_light);
+                secondaryActionDescription = resources.getString(R.string.video_chat);
             } else if ((entry.chatCapability & Im.CAPABILITY_HAS_VOICE) != 0) {
                 secondaryActionIcon =
                         resources.getDrawable(R.drawable.sym_action_audiochat_holo_light);
+                secondaryActionDescription = resources.getString(R.string.audio_chat);
             }
 
             final View secondaryActionViewContainer = views.secondaryActionViewContainer;
             if (entry.secondaryIntent != null && secondaryActionIcon != null) {
                 secondaryActionView.setImageDrawable(secondaryActionIcon);
+                secondaryActionView.setContentDescription(secondaryActionDescription);
                 secondaryActionViewContainer.setTag(entry);
                 secondaryActionViewContainer.setVisibility(View.VISIBLE);
                 views.secondaryActionDivider.setVisibility(View.VISIBLE);
diff --git a/src/com/android/contacts/editor/ContactEditorUtils.java b/src/com/android/contacts/editor/ContactEditorUtils.java
index 0e27223..05a041d 100644
--- a/src/com/android/contacts/editor/ContactEditorUtils.java
+++ b/src/com/android/contacts/editor/ContactEditorUtils.java
@@ -19,7 +19,6 @@
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.AccountWithDataSet;
-import com.android.contacts.test.NeededForTesting;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
@@ -32,6 +31,7 @@
 import android.content.SharedPreferences;
 import android.preference.PreferenceManager;
 import android.text.TextUtils;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -39,9 +39,6 @@
 
 /**
  * Utility methods for the "account changed" notification in the new contact creation flow.
- *
- * TODO Remove all the "@VisibleForTesting"s once they're actually used in the app.
- *      (Until then we need them to avoid "no such method" in tests)
  */
 public class ContactEditorUtils {
     private static final String TAG = "ContactEditorUtils";
@@ -82,6 +79,18 @@
                 .remove(KEY_ANYTHING_SAVED).apply();
     }
 
+    void removeDefaultAccountForTest() {
+        mPrefs.edit().remove(KEY_DEFAULT_ACCOUNT).apply();
+    }
+
+    /**
+     * Sets the {@link #KEY_KNOWN_ACCOUNTS} and {@link #KEY_DEFAULT_ACCOUNT} preference values to
+     * empty strings to reset the state of the preferences file.
+     */
+    private void resetPreferenceValues() {
+        mPrefs.edit().putString(KEY_KNOWN_ACCOUNTS, "").putString(KEY_DEFAULT_ACCOUNT, "").apply();
+    }
+
     private List<AccountWithDataSet> getWritableAccounts() {
         return mAccountTypes.getAccounts(true);
     }
@@ -103,15 +112,23 @@
      * @param defaultAccount the account used to save a newly created contact.  Or pass {@code null}
      *     If the user selected "local only".
      */
-    @NeededForTesting
     public void saveDefaultAndAllAccounts(AccountWithDataSet defaultAccount) {
-        mPrefs.edit()
-                .putBoolean(KEY_ANYTHING_SAVED, true)
-                .putString(
-                        KEY_KNOWN_ACCOUNTS,AccountWithDataSet.stringifyList(getWritableAccounts()))
-                .putString(KEY_DEFAULT_ACCOUNT,
-                        (defaultAccount == null) ? "" : defaultAccount.stringify())
-                .apply();
+        final SharedPreferences.Editor editor = mPrefs.edit()
+                .putBoolean(KEY_ANYTHING_SAVED, true);
+
+        if (defaultAccount == null) {
+            // If the default is "local only", there should be no writable accounts.
+            // This should always be the case with our spec, but because we load the account list
+            // asynchronously using a worker thread, it is possible that there are accounts at this
+            // point. So if the default is null always clear the account list.
+            editor.putString(KEY_KNOWN_ACCOUNTS, "");
+            editor.putString(KEY_DEFAULT_ACCOUNT, "");
+        } else {
+            editor.putString(KEY_KNOWN_ACCOUNTS,
+                    AccountWithDataSet.stringifyList(getWritableAccounts()));
+            editor.putString(KEY_DEFAULT_ACCOUNT, defaultAccount.stringify());
+        }
+        editor.apply();
     }
 
     /**
@@ -123,13 +140,20 @@
      *
      * Also note that the returned account may have been removed already.
      */
-    @NeededForTesting
     public AccountWithDataSet getDefaultAccount() {
         final String saved = mPrefs.getString(KEY_DEFAULT_ACCOUNT, null);
         if (TextUtils.isEmpty(saved)) {
             return null;
         }
-        return AccountWithDataSet.unstringify(saved);
+        try {
+            return AccountWithDataSet.unstringify(saved);
+        } catch (IllegalArgumentException exception) {
+            Log.e(TAG, "Error with retrieving default account " + exception.toString());
+            // unstringify()can throw an exception if the string is not in an expected format.
+            // Hence, if the preferences file is corrupt, just reset the preference values
+            resetPreferenceValues();
+            return null;
+        }
     }
 
     /**
@@ -153,7 +177,15 @@
         if (TextUtils.isEmpty(saved)) {
             return EMPTY_ACCOUNTS;
         }
-        return AccountWithDataSet.unstringifyList(saved);
+        try {
+            return AccountWithDataSet.unstringifyList(saved);
+        } catch (IllegalArgumentException exception) {
+            Log.e(TAG, "Error with retrieving saved accounts " + exception.toString());
+            // unstringifyList()can throw an exception if the string is not in an expected format.
+            // Hence, if the preferences file is corrupt, just reset the preference values
+            resetPreferenceValues();
+            return EMPTY_ACCOUNTS;
+        }
     }
 
     /**
@@ -161,12 +193,12 @@
      * - If it's the first launch.
      * - Or, if an account has been added.
      * - Or, if the default account has been removed.
+     * (And some extra sanity check)
      *
      * Note if this method returns {@code false}, the caller can safely assume that
      * {@link #getDefaultAccount} will return a valid account.  (Either an account which still
      * exists, or {@code null} which should be interpreted as "local only".)
      */
-    @NeededForTesting
     public boolean shouldShowAccountChangedNotification() {
         if (isFirstLaunch()) {
             return true;
@@ -174,14 +206,27 @@
 
         // Account added?
         final List<AccountWithDataSet> savedAccounts = getSavedAccounts();
-        for (AccountWithDataSet account : getWritableAccounts()) {
+        final List<AccountWithDataSet> currentWritableAccounts = getWritableAccounts();
+        for (AccountWithDataSet account : currentWritableAccounts) {
             if (!savedAccounts.contains(account)) {
                 return true; // New account found.
             }
         }
 
+        final AccountWithDataSet defaultAccount = getDefaultAccount();
+
         // Does default account still exist?
-        if (!isValidAccount(getDefaultAccount())) {
+        if (!isValidAccount(defaultAccount)) {
+            return true;
+        }
+
+        // If there is an inconsistent state in the preferences file - default account is null
+        // ("local" account) while there are multiple accounts, then show the notification dialog.
+        // This shouldn't ever happen, but this should allow the user can get back into a normal
+        // state after they respond to the notification.
+        if (defaultAccount == null && currentWritableAccounts.size() > 0) {
+            Log.e(TAG, "Preferences file in an inconsistent state, request that the default account"
+                    + " and current writable accounts be saved again");
             return true;
         }
 
@@ -207,7 +252,6 @@
      * {@link Activity#onActivityResult} or {@link android.app.Fragment#onActivityResult} to
      * get the result.
      */
-    @NeededForTesting
     public Intent createAddWritableAccountIntent() {
         return AccountManager.newChooseAccountIntent(
                 null, // selectedAccount
@@ -231,7 +275,6 @@
      * will never have {@link AccountWithDataSet#dataSet} set, as there's no way to create an
      * extension package account from setup wizard.
      */
-    @NeededForTesting
     public AccountWithDataSet getCreatedAccount(int resultCode, Intent resultData) {
         // Javadoc doesn't say anything about resultCode but that the data intent will be non null
         // on success.
@@ -246,4 +289,3 @@
         return new AccountWithDataSet(accountName, accountType, null);
     }
 }
-
diff --git a/src/com/android/contacts/editor/KindSectionView.java b/src/com/android/contacts/editor/KindSectionView.java
index 686939c..eb8a0a7 100644
--- a/src/com/android/contacts/editor/KindSectionView.java
+++ b/src/com/android/contacts/editor/KindSectionView.java
@@ -224,7 +224,7 @@
     }
 
     protected void updateAddFooterVisible() {
-        if (!mReadOnly && mKind.isList) {
+        if (!mReadOnly && (mKind.typeOverallMax != 1)) {
             // First determine whether there are any existing empty editors.
             updateEmptyEditors();
             // If there are no existing empty editors and it's possible to add
@@ -294,7 +294,7 @@
     public void addItem() {
         ValuesDelta values = null;
         // If this is a list, we can freely add. If not, only allow adding the first.
-        if (!mKind.isList) {
+        if (mKind.typeOverallMax == 1) {
             if (getEditorCount() == 1) {
                 return;
             }
diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java
index 3a40a0f..c9c6699 100644
--- a/src/com/android/contacts/editor/RawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -422,7 +422,7 @@
                 }
                 DataKind kind = sectionView.getKind();
                 // not a list and already exists? ignore
-                if (!kind.isList && sectionView.getEditorCount() != 0) {
+                if ((kind.typeOverallMax == 1) && sectionView.getEditorCount() != 0) {
                     continue;
                 }
                 if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(kind.mimeType)) {
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index 062c25d..5627aa5 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -183,7 +183,7 @@
             } else {
                 fieldView.setMinHeight(mMinFieldHeight);
             }
-            fieldView.setTextAppearance(getContext(), kind.textAppearanceResourceId);
+            fieldView.setTextAppearance(getContext(), android.R.style.TextAppearance_Medium);
             fieldView.setGravity(Gravity.TOP);
             mFieldEditTexts[index] = fieldView;
             fieldView.setId(vig.getId(state, kind, entry, index));
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;
+    }
 }
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index 15158dc..7871dbb 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -359,7 +359,6 @@
         public boolean optional;
         public boolean shortForm;
         public boolean longForm;
-        public boolean isFullName;
 
         public EditField(String column, int titleRes) {
             this.column = column;
@@ -391,11 +390,6 @@
             return this;
         }
 
-        public EditField setIsFullName(boolean isFullName) {
-            this.isFullName = isFullName;
-            return this;
-        }
-
         public boolean isMultiLine() {
             return (inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) != 0;
         }
diff --git a/src/com/android/contacts/model/AccountWithDataSet.java b/src/com/android/contacts/model/AccountWithDataSet.java
index e379346..784e870 100644
--- a/src/com/android/contacts/model/AccountWithDataSet.java
+++ b/src/com/android/contacts/model/AccountWithDataSet.java
@@ -150,11 +150,13 @@
 
     /**
      * Unpack a string created by {@link #stringify}.
+     *
+     * @throws IllegalArgumentException if it's an invalid string.
      */
     public static AccountWithDataSet unstringify(String s) {
         final String[] array = STRINGIFY_SEPARATOR_PAT.split(s, 3);
         if (array.length < 3) {
-            throw new IllegalArgumentException("Invalid string");
+            throw new IllegalArgumentException("Invalid string " + s);
         }
         return new AccountWithDataSet(array[0], array[1],
                 TextUtils.isEmpty(array[2]) ? null : array[2]);
@@ -178,6 +180,8 @@
 
     /**
      * Unpack a list of {@link AccountWithDataSet} into a string.
+     *
+     * @throws IllegalArgumentException if it's an invalid string.
      */
     public static List<AccountWithDataSet> unstringifyList(String s) {
         final ArrayList<AccountWithDataSet> ret = Lists.newArrayList();
diff --git a/src/com/android/contacts/model/BaseAccountType.java b/src/com/android/contacts/model/BaseAccountType.java
index b599c66..a685dee 100644
--- a/src/com/android/contacts/model/BaseAccountType.java
+++ b/src/com/android/contacts/model/BaseAccountType.java
@@ -95,8 +95,7 @@
 
     protected DataKind addDataKindStructuredName(Context context) {
         DataKind kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
-                R.string.nameLabelsGroup, -1, -1, true, R.layout.structured_name_editor_view,
-                android.R.style.TextAppearance_Medium));
+                R.string.nameLabelsGroup, -1, true, R.layout.structured_name_editor_view));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
 
@@ -125,14 +124,13 @@
 
     protected DataKind addDataKindDisplayName(Context context) {
         DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
-                R.string.nameLabelsGroup, -1, -1, true, R.layout.text_fields_editor_view,
-                android.R.style.TextAppearance_Medium));
+                R.string.nameLabelsGroup, -1, true, R.layout.text_fields_editor_view));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
 
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(StructuredName.DISPLAY_NAME,
-                R.string.full_name, FLAGS_PERSON_NAME).setShortForm(true).setIsFullName(true));
+                R.string.full_name, FLAGS_PERSON_NAME).setShortForm(true));
 
         boolean displayOrderPrimary =
                 context.getResources().getBoolean(R.bool.config_editor_field_order_primary);
@@ -166,8 +164,7 @@
 
     protected DataKind addDataKindPhoneticName(Context context) {
         DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
-                R.string.name_phonetic, -1, -1, true, R.layout.phonetic_name_editor_view,
-                android.R.style.TextAppearance_Medium));
+                R.string.name_phonetic, -1, true, R.layout.phonetic_name_editor_view));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
 
@@ -186,9 +183,8 @@
 
     protected DataKind addDataKindNickname(Context context) {
         DataKind kind = addKind(new DataKind(Nickname.CONTENT_ITEM_TYPE,
-                    R.string.nicknameLabelsGroup, -1, 115, true,
-                    R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
-        kind.isList = false;
+                    R.string.nicknameLabelsGroup, 115, true, R.layout.text_fields_editor_view));
+        kind.typeOverallMax = 1;
         kind.actionHeader = new SimpleInflater(R.string.nicknameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
         kind.defaultValues = new ContentValues();
@@ -203,9 +199,9 @@
 
     protected DataKind addDataKindPhone(Context context) {
         DataKind kind = addKind(new DataKind(Phone.CONTENT_ITEM_TYPE, R.string.phoneLabelsGroup,
-                android.R.drawable.sym_action_call, 10, true,
-                R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+                10, true, R.layout.text_fields_editor_view));
         kind.iconAltRes = R.drawable.ic_text_holo_light;
+        kind.iconAltDescriptionRes = R.string.sms;
         kind.actionHeader = new PhoneActionInflater();
         kind.actionAltHeader = new PhoneActionAltInflater();
         kind.actionBody = new SimpleInflater(Phone.NUMBER);
@@ -243,8 +239,7 @@
 
     protected DataKind addDataKindEmail(Context context) {
         DataKind kind = addKind(new DataKind(Email.CONTENT_ITEM_TYPE, R.string.emailLabelsGroup,
-                R.drawable.sym_action_email_holo_light, 15, true,
-                R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+                15, true, R.layout.text_fields_editor_view));
         kind.actionHeader = new EmailActionInflater();
         kind.actionBody = new SimpleInflater(Email.DATA);
         kind.typeColumn = Email.TYPE;
@@ -264,8 +259,7 @@
 
     protected DataKind addDataKindStructuredPostal(Context context) {
         DataKind kind = addKind(new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
-                R.string.postalLabelsGroup, R.drawable.sym_action_show_map_holo_light, 25,
-                true, R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+                R.string.postalLabelsGroup, 25, true, R.layout.text_fields_editor_view));
         kind.actionHeader = new PostalActionInflater();
         kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
         kind.typeColumn = StructuredPostal.TYPE;
@@ -285,9 +279,8 @@
     }
 
     protected DataKind addDataKindIm(Context context) {
-        DataKind kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup,
-                    R.drawable.sym_action_talk_holo_light, 20, true,
-                    R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+        DataKind kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup, 20, true,
+                    R.layout.text_fields_editor_view));
         kind.actionHeader = new ImActionInflater();
         kind.actionBody = new SimpleInflater(Im.DATA);
 
@@ -318,11 +311,11 @@
 
     protected DataKind addDataKindOrganization(Context context) {
         DataKind kind = addKind(new DataKind(Organization.CONTENT_ITEM_TYPE,
-                    R.string.organizationLabelsGroup, -1, 5, true,
-                    R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+                    R.string.organizationLabelsGroup, 5, true,
+                    R.layout.text_fields_editor_view));
         kind.actionHeader = new SimpleInflater(Organization.COMPANY);
         kind.actionBody = new SimpleInflater(Organization.TITLE);
-        kind.isList = false;
+        kind.typeOverallMax = 1;
 
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
@@ -334,8 +327,7 @@
     }
 
     protected DataKind addDataKindPhoto(Context context) {
-        DataKind kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, -1, true, -1,
-                    android.R.style.TextAppearance_Medium));
+        DataKind kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, true, -1));
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
         return kind;
@@ -343,9 +335,8 @@
 
     protected DataKind addDataKindNote(Context context) {
         DataKind kind = addKind(new DataKind(Note.CONTENT_ITEM_TYPE,
-                    R.string.label_notes, -1, 110, true,
-                    R.layout.text_fields_editor_view, android.R.style.TextAppearance_Small));
-        kind.isList = false;
+                    R.string.label_notes, 110, true, R.layout.text_fields_editor_view));
+        kind.typeOverallMax = 1;
         kind.actionHeader = new SimpleInflater(R.string.label_notes);
         kind.actionBody = new SimpleInflater(Note.NOTE);
         kind.fieldList = Lists.newArrayList();
@@ -356,8 +347,7 @@
 
     protected DataKind addDataKindWebsite(Context context) {
         DataKind kind = addKind(new DataKind(Website.CONTENT_ITEM_TYPE,
-                R.string.websiteLabelsGroup, R.drawable.sym_action_goto_website_holo_light, 120,
-                true, R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+                R.string.websiteLabelsGroup, 120, true, R.layout.text_fields_editor_view));
         kind.actionHeader = new SimpleInflater(R.string.websiteLabelsGroup);
         kind.actionBody = new SimpleInflater(Website.URL);
         kind.defaultValues = new ContentValues();
@@ -379,10 +369,9 @@
         // the android:icon attribute of the SIP-related
         // intent-filters in the Phone app's manifest.
         DataKind kind = addKind(new DataKind(SipAddress.CONTENT_ITEM_TYPE,
-                    R.string.label_sip_address, android.R.drawable.sym_action_call, 130, true,
-                    R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+                    R.string.label_sip_address, 130, true, R.layout.text_fields_editor_view));
 
-        kind.isList = false;
+        kind.typeOverallMax = 1;
         kind.actionHeader = new SimpleInflater(R.string.label_sip_address);
         kind.actionBody = new SimpleInflater(SipAddress.SIP_ADDRESS);
         kind.fieldList = Lists.newArrayList();
@@ -395,10 +384,9 @@
     protected DataKind addDataKindGroupMembership(Context context) {
         DataKind kind = getKindForMimetype(GroupMembership.CONTENT_ITEM_TYPE);
         kind = addKind(new DataKind(GroupMembership.CONTENT_ITEM_TYPE,
-                R.string.groupsLabel, android.R.drawable.sym_contact_card, 999, true, -1,
-                android.R.style.TextAppearance_Medium));
+                R.string.groupsLabel, 999, true, -1));
 
-        kind.isList = false;
+        kind.typeOverallMax = 1;
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(GroupMembership.GROUP_ROW_ID, -1, -1));
 
diff --git a/src/com/android/contacts/model/DataKind.java b/src/com/android/contacts/model/DataKind.java
index b0b3f38..70f43ab 100644
--- a/src/com/android/contacts/model/DataKind.java
+++ b/src/com/android/contacts/model/DataKind.java
@@ -42,17 +42,11 @@
     public String resPackageName;
     public String mimeType;
     public int titleRes;
-    public int iconRes;
     public int iconAltRes;
+    public int iconAltDescriptionRes;
     public int weight;
     public boolean editable;
 
-    /**
-     * If this is true (default), the user can add and remove values.
-     * If false, the editor will always show a single field (which might be empty).
-     */
-    public boolean isList;
-
     public StringInflater actionHeader;
     public StringInflater actionAltHeader;
     public StringInflater actionBody;
@@ -63,7 +57,6 @@
 
     /**
      * Maximum number of values allowed in the list. -1 represents infinity.
-     * If {@link DataKind#isList} is false, this value is ignored.
      */
     public int typeOverallMax;
 
@@ -75,9 +68,6 @@
     /** Layout resource id for an editor view to edit this {@link DataKind}. */
     public final int editorLayoutResourceId;
 
-    /** Text appearance resource id for the value of a field in this {@link DataKind}. */
-    public final int textAppearanceResourceId;
-
     /**
      * If this is a date field, this specifies the format of the date when saving. The
      * date includes year, month and day. If this is not a date field or the date field is not
@@ -94,19 +84,15 @@
 
     public DataKind() {
         editorLayoutResourceId = R.layout.text_fields_editor_view;
-        textAppearanceResourceId = android.R.style.TextAppearance_Medium;
     }
 
-    public DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable,
-            int editorLayoutResourceId, int textAppearanceResourceId) {
+    public DataKind(String mimeType, int titleRes, int weight, boolean editable,
+            int editorLayoutResourceId) {
         this.mimeType = mimeType;
         this.titleRes = titleRes;
-        this.iconRes = iconRes;
         this.weight = weight;
         this.editable = editable;
-        this.isList = true;
         this.typeOverallMax = -1;
         this.editorLayoutResourceId = editorLayoutResourceId;
-        this.textAppearanceResourceId = textAppearanceResourceId;
     }
 }
\ No newline at end of file
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index 8582ed8..289ca54 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -694,7 +694,7 @@
 
             ArrayList<ValuesDelta> entries = state.getMimeEntries(mimeType);
 
-            if (kind.isList || GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            if ((kind.typeOverallMax != 1) || GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
                 // Check for duplicates
                 boolean addEntry = true;
                 int count = 0;
@@ -994,10 +994,7 @@
             return null;
         }
 
-        int typeOverallMax = kind.typeOverallMax;
-        if (!kind.isList) {
-            typeOverallMax = 1;
-        }
+        final int typeOverallMax = kind.typeOverallMax;
         if (typeOverallMax >= 0 && (mimeEntries.size() > typeOverallMax)) {
             ArrayList<ValuesDelta> newMimeEntries = new ArrayList<ValuesDelta>(typeOverallMax);
             for (int i = 0; i < typeOverallMax; i++) {
@@ -1350,7 +1347,7 @@
             Log.w(TAG, "Default type isn't available for mimetype " + newDataKind.mimeType);
         }
 
-        final int typeOverallMax = newDataKind.isList ? newDataKind.typeOverallMax : 1;
+        final int typeOverallMax = newDataKind.typeOverallMax;
 
         // key: type, value: the number of current entries.
         final Map<Integer, Integer> currentEntryCount = new HashMap<Integer, Integer>();
diff --git a/src/com/android/contacts/model/ExchangeAccountType.java b/src/com/android/contacts/model/ExchangeAccountType.java
index 4a0e7a0..c109fea 100644
--- a/src/com/android/contacts/model/ExchangeAccountType.java
+++ b/src/com/android/contacts/model/ExchangeAccountType.java
@@ -64,8 +64,7 @@
     @Override
     protected DataKind addDataKindStructuredName(Context context) {
         DataKind kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
-                R.string.nameLabelsGroup, -1, -1, true,
-                R.layout.structured_name_editor_view, android.R.style.TextAppearance_Medium));
+                R.string.nameLabelsGroup, -1, true, R.layout.structured_name_editor_view));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
 
@@ -94,8 +93,7 @@
     @Override
     protected DataKind addDataKindDisplayName(Context context) {
         DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
-                R.string.nameLabelsGroup, -1, -1, true,
-                R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+                R.string.nameLabelsGroup, -1, true, R.layout.text_fields_editor_view));
 
         boolean displayOrderPrimary =
                 context.getResources().getBoolean(R.bool.config_editor_field_order_primary);
@@ -128,8 +126,7 @@
     @Override
     protected DataKind addDataKindPhoneticName(Context context) {
         DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
-                R.string.name_phonetic, -1, -1, true,
-                R.layout.phonetic_name_editor_view, android.R.style.TextAppearance_Medium));
+                R.string.name_phonetic, -1, true, R.layout.phonetic_name_editor_view));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
 
@@ -148,7 +145,7 @@
     protected DataKind addDataKindNickname(Context context) {
         final DataKind kind = super.addDataKindNickname(context);
 
-        kind.isList = false;
+        kind.typeOverallMax = 1;
 
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(Nickname.NAME, R.string.nicknameLabelsGroup,
@@ -259,7 +256,7 @@
     protected DataKind addDataKindOrganization(Context context) {
         final DataKind kind = super.addDataKindOrganization(context);
 
-        kind.isList = false;
+        kind.typeOverallMax = 1;
 
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(Organization.COMPANY, R.string.ghostData_company,
@@ -294,12 +291,12 @@
 
     protected DataKind addDataKindEvent(Context context) {
         DataKind kind = addKind(
-                new DataKind(Event.CONTENT_ITEM_TYPE, R.string.eventLabelsGroup, -1, 150, true,
-                R.layout.event_field_editor_view, android.R.style.TextAppearance_Medium));
+                new DataKind(Event.CONTENT_ITEM_TYPE, R.string.eventLabelsGroup, 150, true,
+                R.layout.event_field_editor_view));
         kind.actionHeader = new EventActionInflater();
         kind.actionBody = new SimpleInflater(Event.START_DATE);
 
-        kind.isList = false;
+        kind.typeOverallMax = 1;
 
         kind.typeColumn = Event.TYPE;
         kind.typeList = Lists.newArrayList();
@@ -317,7 +314,7 @@
     protected DataKind addDataKindWebsite(Context context) {
         final DataKind kind = super.addDataKindWebsite(context);
 
-        kind.isList = false;
+        kind.typeOverallMax = 1;
 
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(Website.URL, R.string.websiteLabelsGroup, FLAGS_WEBSITE));
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/ExternalAccountType.java
index 0518ea5..73aa773 100644
--- a/src/com/android/contacts/model/ExternalAccountType.java
+++ b/src/com/android/contacts/model/ExternalAccountType.java
@@ -121,11 +121,13 @@
         iconRes = resolveExternalResId(context, mAccountTypeIconAttribute,
                 this.resPackageName, ATTR_ACCOUNT_ICON);
 
-        // Bring in name and photo from fallback source, which are non-optional
-        addDataKindStructuredName(context);
-        addDataKindDisplayName(context);
-        addDataKindPhoneticName(context);
-        addDataKindPhoto(context);
+        if (!mHasEditSchema) {
+            // Bring in name and photo from fallback source, which are non-optional
+            addDataKindStructuredName(context);
+            addDataKindDisplayName(context);
+            addDataKindPhoneticName(context);
+            addDataKindPhoto(context);
+        }
 
         // If we reach this point, the account type has been successfully initialized.
         mInitSuccessful = true;
@@ -287,8 +289,6 @@
 
                     kind.mimeType = a
                             .getString(com.android.internal.R.styleable.ContactsDataKind_mimeType);
-                    kind.iconRes = a.getResourceId(
-                            com.android.internal.R.styleable.ContactsDataKind_icon, -1);
 
                     final String summaryColumn = a.getString(
                             com.android.internal.R.styleable.ContactsDataKind_summaryColumn);
diff --git a/src/com/android/contacts/model/GoogleAccountType.java b/src/com/android/contacts/model/GoogleAccountType.java
index cee43dd..094312b 100644
--- a/src/com/android/contacts/model/GoogleAccountType.java
+++ b/src/com/android/contacts/model/GoogleAccountType.java
@@ -109,8 +109,7 @@
 
     private DataKind addDataKindRelation(Context context) {
         DataKind kind = addKind(new DataKind(Relation.CONTENT_ITEM_TYPE,
-                R.string.relationLabelsGroup, -1, 160, true,
-                R.layout.text_fields_editor_view, android.R.style.TextAppearance_Medium));
+                R.string.relationLabelsGroup, 160, true, R.layout.text_fields_editor_view));
         kind.actionHeader = new RelationActionInflater();
         kind.actionBody = new SimpleInflater(Relation.NAME);
 
@@ -145,8 +144,7 @@
 
     private DataKind addDataKindEvent(Context context) {
         DataKind kind = addKind(new DataKind(Event.CONTENT_ITEM_TYPE,
-                    R.string.eventLabelsGroup, -1, 150, true,
-                    R.layout.event_field_editor_view, android.R.style.TextAppearance_Medium));
+                    R.string.eventLabelsGroup, 150, true, R.layout.event_field_editor_view));
         kind.actionHeader = new EventActionInflater();
         kind.actionBody = new SimpleInflater(Event.START_DATE);
 
diff --git a/src/com/android/contacts/quickcontact/Action.java b/src/com/android/contacts/quickcontact/Action.java
index f6e282c..b2d869d 100644
--- a/src/com/android/contacts/quickcontact/Action.java
+++ b/src/com/android/contacts/quickcontact/Action.java
@@ -35,6 +35,9 @@
     /** Returns an icon that can be clicked for the alternate action. */
     public Drawable getAlternateIcon();
 
+    /** Returns the content description of the icon for the alternate action. */
+    public String getAlternateIconDescription();
+
     /** Build an {@link Intent} that will perform this action. */
     public Intent getIntent();
 
diff --git a/src/com/android/contacts/quickcontact/DataAction.java b/src/com/android/contacts/quickcontact/DataAction.java
index 84a34bd..266fd02 100644
--- a/src/com/android/contacts/quickcontact/DataAction.java
+++ b/src/com/android/contacts/quickcontact/DataAction.java
@@ -57,6 +57,7 @@
     private CharSequence mSubtitle;
     private Intent mIntent;
     private Intent mAlternateIntent;
+    private int mAlternateIconDescriptionRes;
     private int mAlternateIconRes;
 
     private Uri mDataUri;
@@ -125,6 +126,7 @@
                         mIntent = phoneIntent;
                         mAlternateIntent = smsIntent;
                         mAlternateIconRes = kind.iconAltRes;
+                        mAlternateIconDescriptionRes = kind.iconAltDescriptionRes;
                     } else if (hasPhone) {
                         mIntent = phoneIntent;
                     } else if (hasSms) {
@@ -198,9 +200,13 @@
                     if (isVideoChatCapable || isAudioChatCapable) {
                         mAlternateIntent = new Intent(
                                 Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call"));
-                        mAlternateIconRes = (isVideoChatCapable
-                                ? R.drawable.sym_action_videochat_holo_light
-                                : R.drawable.sym_action_audiochat_holo_light);
+                        if (isVideoChatCapable) {
+                            mAlternateIconRes = R.drawable.sym_action_videochat_holo_light;
+                            mAlternateIconDescriptionRes = R.string.video_chat;
+                        } else {
+                            mAlternateIconRes = R.drawable.sym_action_audiochat_holo_light;
+                            mAlternateIconDescriptionRes = R.string.audio_chat;
+                        }
                     }
                 }
             }
@@ -290,6 +296,12 @@
     }
 
     @Override
+    public String getAlternateIconDescription() {
+        if (mAlternateIconDescriptionRes == 0) return null;
+        return mContext.getResources().getString(mAlternateIconDescriptionRes);
+    }
+
+    @Override
     public Intent getIntent() {
         return mIntent;
     }
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index fbca129..0588c7d 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -225,9 +225,6 @@
         // find and prepare correct header view
         mPhotoContainer = findViewById(R.id.photo_container);
         setHeaderNameText(R.id.name, R.string.missing_name);
-        setHeaderText(R.id.status, null);
-        setHeaderText(R.id.timestamp, null);
-        setHeaderImage(R.id.presence, null);
 
         // Start background query for data, but only select photo rows when they
         // directly match the super-primary PHOTO_ID.
@@ -477,16 +474,9 @@
         }
 
         if (cursor.moveToLast()) {
-            // Read contact information from last data row
+            // Read contact name from last data row
             final String name = cursor.getString(DataQuery.DISPLAY_NAME);
-            final int presence = cursor.getInt(DataQuery.CONTACT_PRESENCE);
-            final int chatCapability = cursor.getInt(DataQuery.CONTACT_CHAT_CAPABILITY);
-            final Drawable statusIcon = ContactPresenceIconUtil.getChatCapabilityIcon(
-                    context, presence, chatCapability);
-
             setHeaderNameText(R.id.name, name);
-            // TODO: Bring this back once we have a design
-//            setHeaderImage(R.id.presence, statusIcon);
         }
 
         if (photoView != null) {
@@ -498,13 +488,6 @@
             }
         }
 
-        // TODO: Bring this back once we have a design
-//        if (status.isValid()) {
-//            // Update status when valid was found
-//            setHeaderText(R.id.status, status.getStatus());
-//            setHeaderText(R.id.timestamp, status.getTimestampLabel(context));
-//        }
-
         // All the mime-types to add.
         final Set<String> containedTypes = new HashSet<String>(mActions.keySet());
         mSortedActionMimeTypes.clear();
@@ -680,8 +663,6 @@
                 RawContacts.DATA_SET,
                 Contacts.STARRED,
                 Contacts.DISPLAY_NAME,
-                Contacts.CONTACT_PRESENCE,
-                Contacts.CONTACT_CHAT_CAPABILITY,
 
                 Data.STATUS,
                 Data.STATUS_RES_PACKAGE,
@@ -708,20 +689,18 @@
         final int DATA_SET = 2;
         final int STARRED = 3;
         final int DISPLAY_NAME = 4;
-        final int CONTACT_PRESENCE = 5;
-        final int CONTACT_CHAT_CAPABILITY = 6;
 
-        final int STATUS = 7;
-        final int STATUS_RES_PACKAGE = 8;
-        final int STATUS_ICON = 9;
-        final int STATUS_LABEL = 10;
-        final int STATUS_TIMESTAMP = 11;
-        final int PRESENCE = 12;
-        final int CHAT_CAPABILITY = 13;
+        final int STATUS = 5;
+        final int STATUS_RES_PACKAGE = 6;
+        final int STATUS_ICON = 7;
+        final int STATUS_LABEL = 8;
+        final int STATUS_TIMESTAMP = 9;
+        final int PRESENCE = 10;
+        final int CHAT_CAPABILITY = 11;
 
-        final int RES_PACKAGE = 14;
-        final int MIMETYPE = 15;
-        final int IS_PRIMARY = 16;
-        final int IS_SUPER_PRIMARY = 17;
+        final int RES_PACKAGE = 12;
+        final int MIMETYPE = 13;
+        final int IS_PRIMARY = 14;
+        final int IS_SUPER_PRIMARY = 15;
     }
 }
diff --git a/src/com/android/contacts/quickcontact/QuickContactListFragment.java b/src/com/android/contacts/quickcontact/QuickContactListFragment.java
index 0d3b644..c6187e9 100644
--- a/src/com/android/contacts/quickcontact/QuickContactListFragment.java
+++ b/src/com/android/contacts/quickcontact/QuickContactListFragment.java
@@ -101,7 +101,6 @@
                                         R.layout.quickcontact_list_item,
                                         parent, false);
 
-
                 // TODO: Put those findViewByIds in a container
                 final TextView text1 = (TextView) resultView.findViewById(
                         android.R.id.text1);
@@ -121,6 +120,7 @@
                 final boolean hasAlternateAction = action.getAlternateIntent() != null;
                 alternateActionDivider.setVisibility(hasAlternateAction ? View.VISIBLE : View.GONE);
                 alternateActionButton.setImageDrawable(action.getAlternateIcon());
+                alternateActionButton.setContentDescription(action.getAlternateIconDescription());
                 alternateActionButton.setVisibility(hasAlternateAction ? View.VISIBLE : View.GONE);
 
                 // Special case for phone numbers in accessibility mode
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/EntityModifierTests.java
index 76d3d84..d6f99ce 100644
--- a/tests/src/com/android/contacts/EntityModifierTests.java
+++ b/tests/src/com/android/contacts/EntityModifierTests.java
@@ -90,14 +90,14 @@
             this.accountType = TEST_ACCOUNT_TYPE;
 
             final DataKind nameKind = new DataKind(StructuredName.CONTENT_ITEM_TYPE,
-                    R.string.nameLabelsGroup, -1, -1, true, -1, -1);
+                    R.string.nameLabelsGroup, -1, true, -1);
             nameKind.typeOverallMax = 1;
             addKind(nameKind);
 
             // Phone allows maximum 2 home, 1 work, and unlimited other, with
             // constraint of 5 numbers maximum.
             final DataKind phoneKind = new DataKind(
-                    Phone.CONTENT_ITEM_TYPE, -1, -1, 10, true, -1, -1);
+                    Phone.CONTENT_ITEM_TYPE, -1, 10, true, -1);
 
             phoneKind.typeOverallMax = 5;
             phoneKind.typeColumn = Phone.TYPE;
@@ -115,15 +115,15 @@
 
             // Email is unlimited
             final DataKind emailKind = new DataKind(
-                    Email.CONTENT_ITEM_TYPE, -1, -1, 10, true, -1, -1);
+                    Email.CONTENT_ITEM_TYPE, -1, 10, true, -1);
             emailKind.typeOverallMax = -1;
             emailKind.fieldList = Lists.newArrayList();
             emailKind.fieldList.add(new EditField(Email.DATA, -1, -1));
             addKind(emailKind);
 
             // IM is only one
-            final DataKind imKind = new DataKind(Im.CONTENT_ITEM_TYPE, -1, -1, 10,
-                    true, -1, -1);
+            final DataKind imKind = new DataKind(Im.CONTENT_ITEM_TYPE, -1, 10,
+                    true, -1);
             imKind.typeOverallMax = 1;
             imKind.fieldList = Lists.newArrayList();
             imKind.fieldList.add(new EditField(Im.DATA, -1, -1));
@@ -131,7 +131,7 @@
 
             // Organization is only one
             final DataKind orgKind = new DataKind(
-                    Organization.CONTENT_ITEM_TYPE, -1, -1, 10, true, -1, -1);
+                    Organization.CONTENT_ITEM_TYPE, -1, 10, true, -1);
             orgKind.typeOverallMax = 1;
             orgKind.fieldList = Lists.newArrayList();
             orgKind.fieldList.add(new EditField(Organization.COMPANY, -1, -1));
diff --git a/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java b/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
index b159f61..909002a 100644
--- a/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
+++ b/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
@@ -53,7 +53,18 @@
             public void fetchCalls() {}
         };
 
-        mAdapter = new TestCallLogAdapter(getContext(), fakeCallFetcher, TEST_COUNTRY_ISO,
+        ContactInfoHelper fakeContactInfoHelper =
+                new ContactInfoHelper(getContext(), TEST_COUNTRY_ISO) {
+                    @Override
+                    public ContactInfo lookupNumber(String number, String countryIso) {
+                        ContactInfo info = new ContactInfo();
+                        info.number = number;
+                        info.formattedNumber = number;
+                        return info;
+                    }
+                };
+
+        mAdapter = new TestCallLogAdapter(getContext(), fakeCallFetcher, fakeContactInfoHelper,
                 TEST_VOICEMAIL_NUMBER);
         // The cursor used in the tests to store the entries to display.
         mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
@@ -202,8 +213,8 @@
         public final List<Request> requests = Lists.newArrayList();
 
         public TestCallLogAdapter(Context context, CallFetcher callFetcher,
-                String currentCountryIso, String voicemailNumber) {
-            super(context, callFetcher, currentCountryIso, voicemailNumber);
+                ContactInfoHelper contactInfoHelper, String voicemailNumber) {
+            super(context, callFetcher, contactInfoHelper, voicemailNumber);
         }
 
         @Override
diff --git a/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java b/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
index 9f4e487..b2cb39c 100644
--- a/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
+++ b/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
@@ -132,7 +132,6 @@
                 Sets.newHashSet(mAccountTypes.mAccounts),
                 toSet(mTarget.getSavedAccounts()));
 
-
         // 1 account
         mAccountTypes.mAccounts = new AccountWithDataSet[]{ACCOUNT_1_A};
         mTarget.saveDefaultAndAllAccounts(ACCOUNT_1_A);
@@ -141,13 +140,19 @@
                 Sets.newHashSet(mAccountTypes.mAccounts),
                 toSet(mTarget.getSavedAccounts()));
 
-        // 2 account
+        // 2 accounts
         mAccountTypes.mAccounts = new AccountWithDataSet[]{ACCOUNT_1_A, ACCOUNT_1_B};
         mTarget.saveDefaultAndAllAccounts(ACCOUNT_1_B);
         assertEquals(ACCOUNT_1_B, mTarget.getDefaultAccount());
         MoreAsserts.assertEquals(
                 Sets.newHashSet(mAccountTypes.mAccounts),
                 toSet(mTarget.getSavedAccounts()));
+
+        // 2 accounts, and save null as the default.  Even though there are accounts, the saved
+        // account list should be empty in this case.
+        mTarget.saveDefaultAndAllAccounts(null);
+        assertNull(mTarget.getDefaultAccount());
+        assertEquals(0, mTarget.getSavedAccounts().size());
     }
 
     public void testIsAccountValid() {
@@ -272,6 +277,21 @@
         assertFalse(mTarget.shouldShowAccountChangedNotification());
     }
 
+    public void testShouldShowAccountChangedNotification_sanity_check() {
+        // Prepare 1 account and save it as the default.
+        setAccountTypes(TYPE1);
+        setAccounts(ACCOUNT_1_A);
+
+        mTarget.saveDefaultAndAllAccounts(ACCOUNT_1_A);
+
+        // Right after a save, the dialog shouldn't show up.
+        assertFalse(mTarget.shouldShowAccountChangedNotification());
+
+        // Remove the default account to emulate broken preferences.
+        mTarget.removeDefaultAccountForTest();
+        assertTrue(mTarget.shouldShowAccountChangedNotification());
+    }
+
     private static <T> Set<T> toSet(Collection<T> collection) {
         Set<T> ret = Sets.newHashSet();
         ret.addAll(collection);