Merge "Use the older way to cope with onInvalidSelection()"
diff --git a/res/drawable/group_list_item_background.xml b/res/drawable/group_list_item_background.xml
new file mode 100644
index 0000000..0e2e604
--- /dev/null
+++ b/res/drawable/group_list_item_background.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:exitFadeDuration="@android:integer/config_mediumAnimTime">
+    <item android:state_activated="true" android:drawable="@drawable/list_activated_holo" />
+    <item android:state_pressed="true" android:drawable="@drawable/list_pressed_holo" />
+    <item android:state_focused="true" android:drawable="@drawable/list_focused_holo" />
+</selector>
\ No newline at end of file
diff --git a/res/layout/group_browse_list_account_header.xml b/res/layout/group_browse_list_account_header.xml
index b1d873d..da6b960 100644
--- a/res/layout/group_browse_list_account_header.xml
+++ b/res/layout/group_browse_list_account_header.xml
@@ -18,6 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:layout_marginRight="?attr/list_item_padding_right"
     android:minHeight="?attr/list_item_header_height"
     android:orientation="vertical">
 
diff --git a/res/layout/group_browse_list_fragment.xml b/res/layout/group_browse_list_fragment.xml
index c390e67..0d8d4f3 100644
--- a/res/layout/group_browse_list_fragment.xml
+++ b/res/layout/group_browse_list_fragment.xml
@@ -20,6 +20,7 @@
     android:layout_height="match_parent"
     android:orientation="vertical">
 
+    <!-- See group_browse_list_item.xml for the reason for the transparent android:listSelector -->
     <view
       android:id="@+id/list"
       class="com.android.contacts.widget.AutoScrollListView"
@@ -32,7 +33,8 @@
       android:layout_weight="1"
       android:fadingEdge="none"
       android:cacheColorHint="@android:color/transparent"
-      android:divider="@null" />
+      android:divider="@null"
+      android:listSelector="@android:color/transparent"/>
 
     <TextView
         android:id="@+id/empty"
diff --git a/res/layout/group_browse_list_item.xml b/res/layout/group_browse_list_item.xml
index 6bac5ea..88ff575 100644
--- a/res/layout/group_browse_list_item.xml
+++ b/res/layout/group_browse_list_item.xml
@@ -14,6 +14,17 @@
      limitations under the License.
 -->
 
+<!--
+    Note: Because this item layout contains the header too, we don't want to highlight the entire
+    thing when pressed or set the activated background to it.  So we disable the default hilighting
+    by setting transparent to android:listSelector for the list view in
+    group_browse_list_fragment.xml, and make the body part "duplicateParentState", and then set the
+    state list drawable to its background, which has the "activated" background (the drawable with
+    the triangular thing on the right side).  Because of this structure, the item view can't have
+    paddingRight, as the body part should touch the right edge.  Instead we make each child have
+    either marginRight or paddingRight.
+-->
+
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
@@ -21,7 +32,6 @@
     android:layout_height="wrap_content"
     android:paddingLeft="?attr/list_item_padding_left"
     android:paddingTop="?attr/list_item_padding_top"
-    android:paddingRight="?attr/list_item_padding_right"
     android:paddingBottom="?attr/list_item_padding_bottom"
     android:minHeight="@dimen/detail_min_line_item_height" >
 
@@ -29,6 +39,7 @@
         android:id="@+id/divider"
         android:layout_width="match_parent"
         android:layout_height="1dip"
+        android:layout_marginRight="?attr/list_item_padding_right"
         android:background="?android:attr/listDivider" />
 
     <include
@@ -40,12 +51,15 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingTop="8dip"
-        android:paddingBottom="8dip">
+        android:paddingBottom="8dip"
+        android:duplicateParentState="true"
+        android:background="@drawable/group_list_item_background"
+        >
 
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:paddingLeft="?attr/list_item_text_indent"
+            android:layout_marginRight="?attr/list_item_padding_right"
             android:orientation="vertical"
             android:layout_toLeftOf="@+id/icons"
             android:layout_alignParentLeft="true"
diff --git a/res/menu/call_log_options.xml b/res/menu/call_log_options.xml
index c75c856..3d0fb6a 100644
--- a/res/menu/call_log_options.xml
+++ b/res/menu/call_log_options.xml
@@ -14,19 +14,23 @@
      limitations under the License.
 -->
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:id="@+id/delete_all"
-        android:icon="@android:drawable/ic_menu_close_clear_cancel"
-        android:title="@string/recentCalls_deleteAll"
-        android:showAsAction="withText" />
 
     <item
         android:id="@+id/show_voicemails_only"
         android:title="@string/menu_show_voicemails_only"
-        android:showAsAction="withText" />
+        android:showAsAction="withText"
+        android:orderInCategory="1" />
 
     <item
         android:id="@+id/show_all_calls"
         android:title="@string/menu_show_all_calls"
-        android:showAsAction="withText" />
+        android:showAsAction="withText"
+        android:orderInCategory="1" />
+
+    <item
+        android:id="@+id/delete_all"
+        android:icon="@android:drawable/ic_menu_close_clear_cancel"
+        android:title="@string/recentCalls_deleteAll"
+        android:showAsAction="withText"
+        android:orderInCategory="1" />
 </menu>
diff --git a/res/menu/dialpad_options.xml b/res/menu/dialpad_options.xml
index 4dc62a8..02b1f7f 100644
--- a/res/menu/dialpad_options.xml
+++ b/res/menu/dialpad_options.xml
@@ -18,22 +18,26 @@
         android:id="@+id/menu_add_contacts"
         android:icon="@android:drawable/ic_menu_add"
         android:title="@string/recentCalls_addToContact"
-        android:showAsAction="withText" />
+        android:showAsAction="withText"
+        android:orderInCategory="1" />
     <item
         android:id="@+id/menu_2s_pause"
         android:icon="@drawable/ic_menu_2sec_pause"
         android:title="@string/add_2sec_pause"
-        android:showAsAction="withText" />
+        android:showAsAction="withText"
+        android:orderInCategory="1" />
 
     <item
         android:id="@+id/menu_add_wait"
         android:icon="@drawable/ic_menu_wait"
         android:title="@string/add_wait"
-        android:showAsAction="withText" />
+        android:showAsAction="withText"
+        android:orderInCategory="1" />
 
     <item
         android:id="@+id/menu_call_settings_dialpad"
         android:title="@string/call_settings"
         android:icon="@drawable/ic_menu_settings_holo_light"
-        android:showAsAction="withText" />
+        android:showAsAction="withText"
+        android:orderInCategory="1" />
 </menu>
diff --git a/res/menu/dialtacts_options.xml b/res/menu/dialtacts_options.xml
index 8a2fd91..f653242 100644
--- a/res/menu/dialtacts_options.xml
+++ b/res/menu/dialtacts_options.xml
@@ -16,15 +16,16 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
     <item
         android:id="@+id/search_on_action_bar"
-        android:title="@string/menu_search"
-        android:icon="@android:drawable/ic_menu_search"
-        android:showAsAction="always" />
+        android:title="@string/menu_all_contacts"
+        android:showAsAction="ifRoom" />
 
+    <!-- This should come after the other menus in CallLog and Dialpad -->
     <item
         android:id="@+id/menu_call_settings"
         android:title="@string/call_settings"
         android:icon="@drawable/ic_menu_settings_holo_light"
-        android:showAsAction="withText" />
+        android:showAsAction="withText"
+        android:orderInCategory="2" />
 
     <item
         android:id="@+id/filter_option"
diff --git a/res/values-sw580dp-w720dp/styles.xml b/res/values-sw580dp-w720dp/styles.xml
index 1bda6aa..151b173 100644
--- a/res/values-sw580dp-w720dp/styles.xml
+++ b/res/values-sw580dp-w720dp/styles.xml
@@ -33,7 +33,7 @@
         <item name="list_item_gap_between_label_and_data">5dip</item>
         <item name="list_item_call_button_padding">14dip</item>
         <item name="list_item_vertical_divider_margin">5dip</item>
-        <item name="list_item_presence_icon_margin">30dip</item>
+        <item name="list_item_presence_icon_margin">4dip</item>
         <item name="list_item_photo_size">64dip</item>
         <item name="list_item_profile_photo_size">80dip</item>
         <item name="list_item_prefix_highlight_color">#729a27</item>
diff --git a/res/values-sw580dp/styles.xml b/res/values-sw580dp/styles.xml
index 62c0efb..4029401 100644
--- a/res/values-sw580dp/styles.xml
+++ b/res/values-sw580dp/styles.xml
@@ -33,7 +33,7 @@
         <item name="list_item_gap_between_label_and_data">5dip</item>
         <item name="list_item_call_button_padding">14dip</item>
         <item name="list_item_vertical_divider_margin">5dip</item>
-        <item name="list_item_presence_icon_margin">18dip</item>
+        <item name="list_item_presence_icon_margin">4dip</item>
         <item name="list_item_photo_size">64dip</item>
         <item name="list_item_profile_photo_size">80dip</item>
         <item name="list_item_prefix_highlight_color">#729a27</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d459f13..113df61 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1007,7 +1007,7 @@
     <string name="call_disambig_title">Call using</string>
 
     <!-- Menu item label for call settings [CHAR LIMIT=30] -->
-    <string name="call_settings">Call settings</string>
+    <string name="call_settings">Settings</string>
 
     <!-- Title for the sms disambiguation dialog -->
     <string name="sms_disambig_title">Text using</string>
@@ -1037,6 +1037,9 @@
     <!-- The menu item to share the currently viewed contact [CHAR LIMIT=30] -->
     <string name="menu_share">Share</string>
 
+    <!-- The menu item to show all contacts in Phone entrance [CHAR LIMIT=30] -->
+    <string name="menu_all_contacts">All contacts</string>
+
     <!-- Dialog title when picking the application to share a contact with. -->
     <string name="share_via">Share contact via</string>
 
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index c3dc593..3d407ac 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -115,7 +115,6 @@
     public void onAttachFragment(Fragment fragment) {
          if (fragment instanceof ContactLoaderFragment) {
             mLoaderFragment = (ContactLoaderFragment) fragment;
-            mLoaderFragment.setRetainInstance(true);
             mLoaderFragment.setListener(mLoaderFragmentListener);
             mLoaderFragment.loadUri(getIntent().getData());
         }
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index a9c6bf7..a03f83f 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -373,7 +373,6 @@
 
             mContactDetailLoaderFragment = getFragment(R.id.contact_detail_loader_fragment);
             mContactDetailLoaderFragment.setListener(mContactDetailLoaderFragmentListener);
-            mContactDetailLoaderFragment.setRetainInstance(true);
 
             mGroupDetailFragment = getFragment(R.id.group_detail_fragment);
             mGroupDetailFragment.setListener(mGroupDetailFragmentListener);
diff --git a/src/com/android/contacts/detail/ContactLoaderFragment.java b/src/com/android/contacts/detail/ContactLoaderFragment.java
index f3c6158..ac22677 100644
--- a/src/com/android/contacts/detail/ContactLoaderFragment.java
+++ b/src/com/android/contacts/detail/ContactLoaderFragment.java
@@ -210,15 +210,9 @@
         }
 
         @Override
-        public void onLoaderReset(Loader<ContactLoader.Result> loader) {
-            mContactData = null;
-            if (mListener != null) {
-                mListener.onDetailsLoaded(mContactData);
-            }
-        }
+        public void onLoaderReset(Loader<ContactLoader.Result> loader) {}
     };
 
-
     @Override
     public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
         inflater.inflate(R.menu.view_contact, menu);
diff --git a/src/com/android/contacts/detail/StreamItemAdapter.java b/src/com/android/contacts/detail/StreamItemAdapter.java
index 074db80..6586b23 100644
--- a/src/com/android/contacts/detail/StreamItemAdapter.java
+++ b/src/com/android/contacts/detail/StreamItemAdapter.java
@@ -59,7 +59,10 @@
 
     @Override
     public int getCount() {
-        return mStreamItems.size() + 2;
+        // The header and title should only be included as items in the list if there are other
+        // stream items.
+        int count = mStreamItems.size();
+        return (count == 0) ? 0 : (count + 2);
     }
 
     @Override
diff --git a/src/com/android/contacts/group/GroupEditorFragment.java b/src/com/android/contacts/group/GroupEditorFragment.java
index 2287557..9cdbe46 100644
--- a/src/com/android/contacts/group/GroupEditorFragment.java
+++ b/src/com/android/contacts/group/GroupEditorFragment.java
@@ -92,6 +92,8 @@
     private static final String KEY_MEMBERS_TO_REMOVE = "membersToRemove";
     private static final String KEY_MEMBERS_TO_DISPLAY = "membersToDisplay";
 
+    private static final String CURRENT_EDITOR_TAG = "currentEditorForAccount";
+
     public static interface Listener {
         /**
          * Group metadata was not found, close the fragment now.
@@ -189,6 +191,7 @@
 
     private boolean mGroupNameIsReadOnly;
     private String mOriginalGroupName = "";
+    private int mLastGroupEditorId;
 
     private MemberListAdapter mMemberListAdapter;
     private ContactPhotoManager mPhotoManager;
@@ -374,10 +377,31 @@
     private void setupEditorForAccount() {
         final AccountType accountType = getAccountType();
         final boolean editable = isGroupMembershipEditable();
+        boolean isNewEditor = false;
         mMemberListAdapter.setIsGroupMembershipEditable(editable);
 
-        View editorView = mLayoutInflater.inflate(editable ?
-                R.layout.group_editor_view : R.layout.external_group_editor_view, mRootView, false);
+        // Since this method can be called multiple time, remove old editor if the editor type
+        // is different from the new one and mark the editor with a tag so it can be found for
+        // removal if needed
+        View editorView;
+        int newGroupEditorId =
+                editable ? R.layout.group_editor_view : R.layout.external_group_editor_view;
+        if (newGroupEditorId != mLastGroupEditorId) {
+            View oldEditorView = mRootView.findViewWithTag(CURRENT_EDITOR_TAG);
+            if (oldEditorView != null) {
+                mRootView.removeView(oldEditorView);
+            }
+            editorView = mLayoutInflater.inflate(newGroupEditorId, mRootView, false);
+            editorView.setTag(CURRENT_EDITOR_TAG);
+            mAutoCompleteAdapter = null;
+            mLastGroupEditorId = newGroupEditorId;
+            isNewEditor = true;
+        } else {
+            editorView = mRootView.findViewWithTag(CURRENT_EDITOR_TAG);
+            if (editorView == null) {
+                throw new IllegalStateException("Group editor view not found");
+            }
+        }
 
         mGroupNameView = (TextView) editorView.findViewById(R.id.group_name);
         mAccountIcon = (ImageView) editorView.findViewById(R.id.account_icon);
@@ -433,8 +457,9 @@
 
         // If the group name is ready only, don't let the user focus on the field.
         mGroupNameView.setFocusable(!mGroupNameIsReadOnly);
-
-        mRootView.addView(editorView);
+        if(isNewEditor) {
+            mRootView.addView(editorView);
+        }
         mStatus = Status.EDITING;
     }
 
@@ -680,9 +705,15 @@
     }
 
     private void addExistingMembers(List<Member> members) {
+
+        // Re-create the list to display
+        mListToDisplay.clear();
         mListToDisplay.addAll(members);
+        mListToDisplay.addAll(mListMembersToAdd);
+        mListToDisplay.removeAll(mListMembersToRemove);
         mMemberListAdapter.notifyDataSetChanged();
 
+
         // Update the autocomplete adapter (if there is one) so these contacts don't get suggested
         if (mAutoCompleteAdapter != null) {
             mAutoCompleteAdapter.updateExistingMembersList(members);
diff --git a/src/com/android/contacts/interactions/GroupNameDialogFragment.java b/src/com/android/contacts/interactions/GroupNameDialogFragment.java
index 93500be..3c65562 100644
--- a/src/com/android/contacts/interactions/GroupNameDialogFragment.java
+++ b/src/com/android/contacts/interactions/GroupNameDialogFragment.java
@@ -76,7 +76,10 @@
     @Override
     public void afterTextChanged(Editable s) {
         AlertDialog dialog = (AlertDialog) getDialog();
-        updateOkButtonState(dialog);
+        // Make sure the dialog has not already been dismissed or destroyed.
+        if (dialog != null) {
+            updateOkButtonState(dialog);
+        }
     }
 
     private void updateOkButtonState(AlertDialog dialog) {
diff --git a/src/com/android/contacts/list/AccountFilterActivity.java b/src/com/android/contacts/list/AccountFilterActivity.java
index fb0cf9e..02abb53 100644
--- a/src/com/android/contacts/list/AccountFilterActivity.java
+++ b/src/com/android/contacts/list/AccountFilterActivity.java
@@ -22,13 +22,24 @@
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.AccountWithDataSet;
+import com.google.android.collect.Lists;
 
 import android.app.ActionBar;
 import android.app.Activity;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.AsyncTaskLoader;
 import android.content.Context;
 import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Bundle;
+import android.provider.BaseColumns;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.RawContacts;
+import android.text.TextUtils;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
@@ -53,9 +64,13 @@
 
     public static final String KEY_EXTRA_CONTACT_LIST_FILTER = "contactListFilter";
 
+    private static final int FILTER_LOADER_ID = 0;
+
     private ListView mListView;
 
-    private List<ContactListFilter> mFilters = new ArrayList<ContactListFilter>();
+    private static final String[] ID_PROJECTION = new String[] {BaseColumns._ID};
+    private static final Uri RAW_CONTACTS_URI_LIMIT_1 = RawContacts.CONTENT_URI.buildUpon()
+            .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, "1").build();
 
     @Override
     protected void onCreate(Bundle icicle) {
@@ -70,38 +85,118 @@
             actionBar.setDisplayHomeAsUpEnabled(true);
         }
 
-        loadAccountFilters();
+        getLoaderManager().initLoader(FILTER_LOADER_ID, null, new MyLoaderCallbacks());
     }
 
-    private void loadAccountFilters() {
-        ArrayList<ContactListFilter> accountFilters = new ArrayList<ContactListFilter>();
-        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
+    private static class FilterLoader extends AsyncTaskLoader<List<ContactListFilter>> {
+        private Context mContext;
+
+        public FilterLoader(Context context) {
+            super(context);
+            mContext = context;
+        }
+
+        @Override
+        public List<ContactListFilter> loadInBackground() {
+            return loadAccountFilters(mContext);
+        }
+
+        @Override
+        protected void onStartLoading() {
+            forceLoad();
+        }
+
+        @Override
+        protected void onStopLoading() {
+            cancelLoad();
+        }
+
+        @Override
+        protected void onReset() {
+            onStopLoading();
+        }
+    }
+
+    private static List<ContactListFilter> loadAccountFilters(Context context) {
+        final ArrayList<ContactListFilter> result = Lists.newArrayList();
+        final ArrayList<ContactListFilter> accountFilters = Lists.newArrayList();
+        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context);
         List<AccountWithDataSet> accounts = accountTypes.getAccounts(false);
         for (AccountWithDataSet account : accounts) {
             AccountType accountType = accountTypes.getAccountType(account.type, account.dataSet);
-            Drawable icon = accountType != null ? accountType.getDisplayIcon(this) : null;
+            if (accountType.isExtension() && !hasAccountData(context, account)) {
+                // Hide extensions with no raw_contacts.
+                continue;
+            }
+            Drawable icon = accountType != null ? accountType.getDisplayIcon(context) : null;
             accountFilters.add(ContactListFilter.createAccountFilter(account.type, account.name,
                     account.dataSet, icon, account.name));
         }
-        final int count = accountFilters.size();
 
+        // Always show "All", even when there's no accounts.  (We may have local contacts)
+        result.add(ContactListFilter.createFilterWithType(
+                ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS));
+
+        final int count = accountFilters.size();
         if (count >= 1) {
-            mFilters.add(ContactListFilter.createFilterWithType(
-                    ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS));
             // If we only have one account, don't show it as "account", instead show it as "all"
             if (count > 1) {
-                mFilters.addAll(accountFilters);
+                result.addAll(accountFilters);
             }
-            mFilters.add(ContactListFilter.createFilterWithType(
+            result.add(ContactListFilter.createFilterWithType(
                     ContactListFilter.FILTER_TYPE_CUSTOM));
         }
+        return result;
+    }
 
-        mListView.setAdapter(new FilterListAdapter(this));
+    private static boolean hasAccountData(Context context, AccountWithDataSet account) {
+        final String BASE_SELECTION =
+                RawContacts.ACCOUNT_TYPE + " = ?" + " AND " + RawContacts.ACCOUNT_NAME + " = ?";
+        final String selection;
+        final String[] args;
+        if (TextUtils.isEmpty(account.dataSet)) {
+            selection = BASE_SELECTION + " AND " + RawContacts.DATA_SET + " IS NULL";
+            args = new String[] {account.type, account.name};
+        } else {
+            selection = BASE_SELECTION + " AND " + RawContacts.DATA_SET + " = ?";
+            args = new String[] {account.type, account.name, account.dataSet};
+        }
+
+        final Cursor c = context.getContentResolver().query(RAW_CONTACTS_URI_LIMIT_1,
+                ID_PROJECTION, selection, args, null);
+        if (c == null) return false;
+        try {
+            return c.moveToFirst();
+        } finally {
+            c.close();
+        }
+    }
+
+    private class MyLoaderCallbacks implements LoaderCallbacks<List<ContactListFilter>> {
+        @Override
+        public Loader<List<ContactListFilter>> onCreateLoader(int id, Bundle args) {
+            return new FilterLoader(AccountFilterActivity.this);
+        }
+
+        @Override
+        public void onLoadFinished(
+                Loader<List<ContactListFilter>> loader, List<ContactListFilter> data) {
+            if (data == null) { // Just in case...
+                Log.e(TAG, "Failed to load filters");
+                return;
+            }
+            mListView.setAdapter(new FilterListAdapter(AccountFilterActivity.this, data));
+        }
+
+        @Override
+        public void onLoaderReset(Loader<List<ContactListFilter>> loader) {
+        }
     }
 
     @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        ContactListFilter filter = mFilters.get(position);
+        final ContactListFilter filter = (ContactListFilter) view.getTag();
+        if (filter == null) return; // Just in case
         if (filter.filterType == ContactListFilter.FILTER_TYPE_CUSTOM) {
             final Intent intent = new Intent(this,
                     CustomContactListFilterActivity.class);
@@ -143,12 +238,14 @@
         }
     }
 
-    private class FilterListAdapter extends BaseAdapter {
-        private LayoutInflater mLayoutInflater;
+    private static class FilterListAdapter extends BaseAdapter {
+        private final List<ContactListFilter> mFilters;
+        private final LayoutInflater mLayoutInflater;
 
-        public FilterListAdapter(Context context) {
+        public FilterListAdapter(Context context, List<ContactListFilter> filters) {
             mLayoutInflater = (LayoutInflater) context.getSystemService
                     (Context.LAYOUT_INFLATER_SERVICE);
+            mFilters = filters;
         }
 
         @Override
@@ -167,7 +264,7 @@
         }
 
         public View getView(int position, View convertView, ViewGroup parent) {
-            ContactListFilterView view;
+            final ContactListFilterView view;
             if (convertView != null) {
                 view = (ContactListFilterView) convertView;
             } else {
@@ -175,9 +272,10 @@
                         R.layout.contact_list_filter_item, parent, false);
             }
             view.setSingleAccount(mFilters.size() == 1);
-            ContactListFilter filter = mFilters.get(position);
+            final ContactListFilter filter = mFilters.get(position);
             view.setContactListFilter(filter);
             view.bindView(true);
+            view.setTag(filter);
             return view;
         }
     }
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index ce1b119..f16102d 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -934,7 +934,7 @@
             mStatusView.setSingleLine(true);
             mStatusView.setEllipsize(getTextEllipsis());
             mStatusView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
-            mStatusView.setTextColor(Color.GRAY);
+            mStatusView.setTextColor(R.color.secondary_text_color);
             addView(mStatusView);
         }
         return mStatusView;
diff --git a/src/com/android/contacts/list/ContactTileAdapter.java b/src/com/android/contacts/list/ContactTileAdapter.java
index 2f0f24b..0755376 100644
--- a/src/com/android/contacts/list/ContactTileAdapter.java
+++ b/src/com/android/contacts/list/ContactTileAdapter.java
@@ -21,7 +21,6 @@
 import com.android.contacts.ContactTileLoaderFactory;
 import com.android.contacts.GroupMemberLoader;
 import com.android.contacts.R;
-import com.android.contacts.list.ContactTileAdapter.DisplayType;
 
 import android.content.ContentUris;
 import android.content.Context;
@@ -34,7 +33,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
-import android.widget.LinearLayout;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import java.util.ArrayList;
@@ -482,8 +481,10 @@
 
     /**
      * Acts as a row item composed of {@link ContactTileView}
+     *
+     * TODO: FREQUENT doesn't really need it.  Just let {@link #getView} return
      */
-    private class ContactTileRow extends LinearLayout {
+    private class ContactTileRow extends FrameLayout {
         private int mItemViewType;
         private int mLayoutResId;
 
@@ -512,8 +513,11 @@
 
             if (getChildCount() <= childIndex) {
                 contactTile = (ContactTileView) inflate(mContext, mLayoutResId, null);
-                contactTile.setLayoutParams(new LinearLayout.LayoutParams(0,
-                        LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f));
+                // Note: the layoutparam set here is only actually used for FREQUENT.
+                // We override onMeasure() for STARRED and we don't care the layout param there.
+                contactTile.setLayoutParams(new FrameLayout.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT));
                 contactTile.setPhotoManager(mPhotoManager);
                 contactTile.setListener(mContactTileListener);
                 addView(contactTile);
@@ -538,6 +542,89 @@
                     break;
             }
         }
+
+        @Override
+        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            switch (mItemViewType) {
+                case ViewTypes.STARRED_WITH_SECONDARY_ACTION:
+                case ViewTypes.STARRED:
+                    onLayoutForTiles(left, top, right, bottom);
+                    return;
+                default:
+                    super.onLayout(changed, left, top, right, bottom);
+                    return;
+            }
+        }
+
+        private void onLayoutForTiles(int left, int top, int right, int bottom) {
+            final int count = getChildCount();
+            final int width = right - left;
+
+            // Just line up children horizontally.
+            int childLeft = 0;
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+
+                // Note MeasuredWidth includes the padding.
+                final int childWidth = child.getMeasuredWidth();
+                child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
+                childLeft += childWidth;
+            }
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            switch (mItemViewType) {
+                case ViewTypes.STARRED_WITH_SECONDARY_ACTION:
+                case ViewTypes.STARRED:
+                    onMeasureForTiles(widthMeasureSpec, heightMeasureSpec);
+                    return;
+                default:
+                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+                    return;
+            }
+        }
+
+        private void onMeasureForTiles(int widthMeasureSpec, int heightMeasureSpec) {
+            final int width = MeasureSpec.getSize(widthMeasureSpec);
+
+            final int childCount = getChildCount();
+            if (childCount == 0) {
+                // Just in case...
+                setMeasuredDimension(width, 0);
+                return;
+            }
+
+            // 1. Calculate image size.
+            //      = ([total width] - [total padding]) / [child count]
+            //
+            // 2. Set it to width/height of each children.
+            //    If we have a remainder, some tiles will have 1 pixel larger width than its height.
+            //
+            // 3. Set the dimensions of itself.
+            //    Let width = given width.
+            //    Let height = image size + bottom paddding.
+
+            final int totalPaddingsInPixels = (mColumnCount - 1) * mPaddingInPixels;
+
+            // Preferred width / height for images (excluding the padding).
+            // The actual width may be 1 pixel larger than this if we have a remainder.
+            final int imageSize = (width - totalPaddingsInPixels) / mColumnCount;
+            final int remainder = width - (imageSize * mColumnCount) - totalPaddingsInPixels;
+
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                final int childWidth = imageSize + child.getPaddingRight()
+                        // Compensate for the remainder
+                        + (i < remainder ? 1 : 0);
+                final int childHeight = imageSize + child.getPaddingBottom();
+                child.measure(
+                        MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)
+                        );
+            }
+            setMeasuredDimension(width, imageSize + getChildAt(0).getPaddingBottom());
+        }
     }
 
     /**
diff --git a/src/com/android/contacts/list/ContactTileStarredView.java b/src/com/android/contacts/list/ContactTileStarredView.java
index 3c0ba42..c017731 100644
--- a/src/com/android/contacts/list/ContactTileStarredView.java
+++ b/src/com/android/contacts/list/ContactTileStarredView.java
@@ -20,7 +20,11 @@
 
 /**
  * A {@link ContactTileStarredView} displays the contact's picture overlayed with their name
- * in a perfect square.
+ * in a square.  The actual dimensions are set by
+ * {@link com.android.contacts.list.ContactTileAdapter.ContactTileRow}.
+ *
+ * TODO Just remove this class.  We probably don't need {@link ContactTileSecondaryTargetView}
+ * either.  (We can probably put the functionality to {@link ContactTileView})
  */
 public class ContactTileStarredView extends ContactTileView {
     private final static String TAG = ContactTileStarredView.class.getSimpleName();
@@ -28,14 +32,4 @@
     public ContactTileStarredView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        // Getting how much space is currently available and telling our
-        // Children to split it.
-        int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
-        int childMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
-        measureChildren(childMeasureSpec, childMeasureSpec);
-        setMeasuredDimension(width, width);
-    }
 }
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index 8ee1aa1..879a89f 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -87,6 +87,10 @@
         return false;
     }
 
+    public boolean isExtension() {
+        return false;
+    }
+
     /**
      * Returns an optional custom edit activity.  The activity class should reside
      * in the sync adapter package as determined by {@link #resPackageName}.
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index 2d821e6..d28d5bb 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -309,7 +309,7 @@
                     // TODO: use syncadapter package instead, since it provides resources
                     Log.d(TAG, "Registering external account type=" + type
                             + ", packageName=" + auth.packageName);
-                    accountType = new ExternalAccountType(mContext, auth.packageName);
+                    accountType = new ExternalAccountType(mContext, auth.packageName, false);
                     if (!((ExternalAccountType) accountType).isInitialized()) {
                         // Skip external account types that couldn't be initialized.
                         continue;
@@ -333,7 +333,7 @@
                 Log.d(TAG, "Registering " + extensionPackages.size() + " extension packages");
                 for (String extensionPackage : extensionPackages) {
                     ExternalAccountType accountType =
-                            new ExternalAccountType(mContext, extensionPackage);
+                            new ExternalAccountType(mContext, extensionPackage, true);
                     if (!accountType.isInitialized()) {
                         // Skip external account types that couldn't be initialized.
                         continue;
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/ExternalAccountType.java
index b6649c9..7fefc44 100644
--- a/src/com/android/contacts/model/ExternalAccountType.java
+++ b/src/com/android/contacts/model/ExternalAccountType.java
@@ -69,6 +69,8 @@
     private static final String ATTR_ACCOUNT_LABEL = "accountTypeLabel";
     private static final String ATTR_ACCOUNT_ICON = "accountTypeIcon";
 
+    private final boolean mIsExtension;
+
     private String mEditContactActivityClassName;
     private String mCreateContactActivityClassName;
     private String mInviteContactActivity;
@@ -84,7 +86,8 @@
     private boolean mInitSuccessful;
     private boolean mHasContactsMetadata;
 
-    public ExternalAccountType(Context context, String resPackageName) {
+    public ExternalAccountType(Context context, String resPackageName, boolean isExtension) {
+        this.mIsExtension = isExtension;
         this.resPackageName = resPackageName;
         this.summaryResPackageName = resPackageName;
 
@@ -127,6 +130,11 @@
         return true;
     }
 
+    @Override
+    public boolean isExtension() {
+        return mIsExtension;
+    }
+
     /**
      * Whether this account type was able to be fully initialized.  This may be false if
      * (for example) the package name associated with the account type could not be found.