Merge "Fix broken behavior around ImportVCardActivity."
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 037af20..da4856c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -260,6 +260,9 @@
          For example, this may be used to set a phone number's label to "Vaction house" -->
     <string name="customLabelPickerTitle">Custom label name</string>
 
+    <!-- The menu item to open the list of groups to display -->
+    <string name="menu_displayGroup">Display options</string>
+
     <!-- Check box label that allows calls to the contact to be sent directly to voicemail -->
     <string name="send_to_voicemail_checkbox">Send calls directly to voicemail</string>
 
@@ -1358,6 +1361,9 @@
     <!-- Contact list filter selection indicating that the list shows all contacts with phone numbers [CHAR LIMIT=64] -->
     <string name="list_filter_phones">Contacts with phone numbers</string>
 
+    <!-- Contact list filter selection indicating that the list shows only the selected contact [CHAR LIMIT=64] -->
+    <string name="list_filter_single">Contact</string>
+
     <!-- Title of the activity that allows the user to customize filtering of contact list [CHAR LIMIT=128] -->
     <string name="custom_list_filter">Define custom view</string>
 
diff --git a/src/com/android/contacts/activities/ContactBrowserActivity.java b/src/com/android/contacts/activities/ContactBrowserActivity.java
index ce484f0..054c50f 100644
--- a/src/com/android/contacts/activities/ContactBrowserActivity.java
+++ b/src/com/android/contacts/activities/ContactBrowserActivity.java
@@ -27,6 +27,7 @@
 import com.android.contacts.list.ContactListFilterController;
 import com.android.contacts.list.ContactsIntentResolver;
 import com.android.contacts.list.ContactsRequest;
+import com.android.contacts.list.CustomContactListFilterActivity;
 import com.android.contacts.list.DefaultContactBrowseListFragment;
 import com.android.contacts.list.DirectoryListLoader;
 import com.android.contacts.list.OnContactBrowserActionListener;
@@ -48,7 +49,6 @@
 import android.content.ContentValues;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -57,7 +57,6 @@
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Intents;
 import android.provider.Settings;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.Menu;
@@ -80,15 +79,15 @@
  */
 public class ContactBrowserActivity extends Activity
         implements View.OnCreateContextMenuListener, ActionBarAdapter.Listener,
-        DialogManager.DialogShowingViewActivity {
+        DialogManager.DialogShowingViewActivity,
+        ContactListFilterController.ContactListFilterListener {
 
     private static final String TAG = "ContactBrowserActivity";
 
     private static final int SUBACTIVITY_NEW_CONTACT = 2;
     private static final int SUBACTIVITY_SETTINGS = 3;
     private static final int SUBACTIVITY_EDIT_CONTACT = 4;
-
-    private static final String KEY_DEFAULT_CONTACT_URI = "defaultSelectedContactUri";
+    private static final int SUBACTIVITY_CUSTOMIZE_FILTER = 5;
 
     private static final int DEFAULT_DIRECTORY_RESULT_LIMIT = 20;
 
@@ -129,6 +128,7 @@
     public ContactBrowserActivity() {
         mIntentResolver = new ContactsIntentResolver(this);
         mContactListFilterController = new ContactListFilterController(this);
+        mContactListFilterController.addListener(this);
     }
 
     @Override
@@ -136,9 +136,11 @@
         if (fragment instanceof ContactBrowseListFragment) {
             mListFragment = (ContactBrowseListFragment)fragment;
             mListFragment.setOnContactListActionListener(new ContactBrowserActionListener());
-            if (mListFragment instanceof DefaultContactBrowseListFragment) {
-                ((DefaultContactBrowseListFragment) mListFragment).setContactListFilterController(
-                        mContactListFilterController);
+            if (mListFragment instanceof DefaultContactBrowseListFragment
+                    && mContactListFilterController != null
+                    && mContactListFilterController.isLoaded()) {
+                ((DefaultContactBrowseListFragment) mListFragment).setFilter(
+                        mContactListFilterController.getFilter());
             }
         } else if (fragment instanceof ContactNoneFragment) {
             mEmptyFragment = (ContactNoneFragment)fragment;
@@ -216,7 +218,9 @@
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
         if (Intent.ACTION_VIEW.equals(intent.getAction())) {
-            Uri uri = intent.getData();
+            mRequest = mIntentResolver.resolveIntent(getIntent());
+
+            Uri uri = mRequest.getContactUri();
             if (uri == null) {
                 return;
             }
@@ -227,27 +231,20 @@
                     mActionBarAdapter.setSearchMode(false);
                 }
             }
-            setSelectedContactUri(uri);
-            setupContactDetailFragment(uri);
+            mListFragment.setSelectedContactUri(uri);
             mListFragment.requestSelectionOnScreen(true);
+            if (mContactContentDisplayed) {
+                setupContactDetailFragment(uri);
+            }
         }
     }
 
-    public void setSelectedContactUri(Uri contactLookupUri) {
-        mListFragment.setSelectedContactUri(contactLookupUri);
-
-        Editor editor = mPrefs.edit();
-        if (contactLookupUri == null) {
-            editor.remove(KEY_DEFAULT_CONTACT_URI);
-        } else {
-            editor.putString(KEY_DEFAULT_CONTACT_URI, contactLookupUri.toString());
+    @Override
+    protected void onStart() {
+        if (mContactListFilterController != null) {
+            mContactListFilterController.startLoading();
         }
-        editor.apply();
-    }
-
-    public Uri getDefaultSelectedContactUri() {
-        String uriString = mPrefs.getString(KEY_DEFAULT_CONTACT_URI, null);
-        return TextUtils.isEmpty(uriString) ? null : Uri.parse(uriString);
+        super.onStart();
     }
 
     private void configureListFragment(boolean fromRequest) {
@@ -315,23 +312,14 @@
             }
         }
 
-        Uri selectUri = null;
         if (fromRequest) {
-            selectUri = mRequest.getContactUri();
+            Uri selectUri = mRequest.getContactUri();
             if (selectUri != null) {
-                setSelectedContactUri(selectUri);
+                mListFragment.setSelectedContactUri(selectUri);
+                mListFragment.requestSelectionOnScreen(false);
             }
         }
 
-        if (selectUri == null && mListFragment.getSelectedContactUri() == null) {
-            selectUri = getDefaultSelectedContactUri();
-        }
-
-        if (selectUri != null) {
-            mListFragment.setSelectedContactUri(selectUri);
-            mListFragment.requestSelectionOnScreen(false);
-        }
-
         if (replaceList) {
             getFragmentManager().openTransaction()
                     .replace(R.id.list_container, mListFragment)
@@ -357,6 +345,48 @@
         }
     }
 
+    @Override
+    public void onContactListFiltersLoaded() {
+        if (mListFragment instanceof DefaultContactBrowseListFragment) {
+            DefaultContactBrowseListFragment fragment =
+                    (DefaultContactBrowseListFragment) mListFragment;
+            restoreListSelection(fragment);
+
+            // Filters have been loaded - now we can start loading the list itself
+            fragment.startLoading();
+        }
+    }
+
+    @Override
+    public void onContactListFilterChanged() {
+        // If there was a request to show a specific contact, that's no longer the case
+        // because the user has explicitly changed the filter.
+        mRequest.setContactUri(null);
+
+        if (mListFragment instanceof DefaultContactBrowseListFragment) {
+            DefaultContactBrowseListFragment fragment =
+                    (DefaultContactBrowseListFragment) mListFragment;
+            restoreListSelection(fragment);
+
+            fragment.reloadData();
+        }
+    }
+
+    private void restoreListSelection(DefaultContactBrowseListFragment fragment) {
+        fragment.setFilter(mContactListFilterController.getFilter());
+        fragment.restoreSelectedUri(mPrefs);
+        fragment.requestSelectionOnScreen(false);
+        if (mContactContentDisplayed) {
+            setupContactDetailFragment(fragment.getSelectedContactUri());
+        }
+    }
+
+    @Override
+    public void onContactListFilterCustomizationRequest() {
+        startActivityForResult(new Intent(this, CustomContactListFilterActivity.class),
+                SUBACTIVITY_CUSTOMIZE_FILTER);
+    }
+
     private void setupContactDetailFragment(final Uri contactLookupUri) {
         if (mDetailFragment != null && contactLookupUri != null
                 && contactLookupUri.equals(mDetailFragment.getUri())) {
@@ -508,7 +538,8 @@
         @Override
         public void onViewContactAction(Uri contactLookupUri) {
             if (mContactContentDisplayed) {
-                setSelectedContactUri(contactLookupUri);
+                mListFragment.setSelectedContactUri(contactLookupUri);
+                mListFragment.saveSelectedUri(mPrefs);
                 setupContactDetailFragment(contactLookupUri);
             } else {
                 startActivity(new Intent(Intent.ACTION_VIEW, contactLookupUri));
@@ -568,6 +599,37 @@
         public void onFinishAction() {
             onBackPressed();
         }
+
+        @Override
+        public void onInvalidSelection() {
+            Uri requestedContactUri = mRequest.getContactUri();
+            if (requestedContactUri != null
+                    && mListFragment instanceof DefaultContactBrowseListFragment) {
+                // If a specific selection was requested, adjust the filter so
+                // that the requested selection is uncoditionally visible.
+                DefaultContactBrowseListFragment fragment =
+                        (DefaultContactBrowseListFragment) mListFragment;
+                ContactListFilter filter = new ContactListFilter(
+                        ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
+                fragment.setFilter(filter);
+                fragment.setSelectedContactUri(requestedContactUri);
+                fragment.saveSelectedUri(mPrefs);
+                fragment.reloadData();
+                if (mContactListFilterController != null) {
+                    mContactListFilterController.setContactListFilter(filter, true);
+                }
+            } else {
+                // Otherwise, choose the first contact on the list and select it
+                requestedContactUri = mListFragment.getFirstContactUri();
+                if (requestedContactUri != null) {
+                    mListFragment.setSelectedContactUri(requestedContactUri);
+                    mListFragment.requestSelectionOnScreen(false);
+                }
+            }
+            if (mContactContentDisplayed) {
+                setupContactDetailFragment(requestedContactUri);
+            }
+        }
     }
 
     private class DetailFragmentListener implements ContactDetailFragment.Listener {
@@ -768,6 +830,12 @@
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         switch (requestCode) {
+            case SUBACTIVITY_CUSTOMIZE_FILTER: {
+                if (resultCode == Activity.RESULT_OK) {
+                    mContactListFilterController.selectCustomFilter();
+                }
+                break;
+            }
             case SUBACTIVITY_EDIT_CONTACT: {
                 mListFragment.requestSelectionOnScreen(true);
                 break;
@@ -776,8 +844,15 @@
             case SUBACTIVITY_NEW_CONTACT: {
                 if (resultCode == RESULT_OK && mContactContentDisplayed) {
                     final Uri newContactUri = data.getData();
-                    setSelectedContactUri(newContactUri);
-                    setupContactDetailFragment(newContactUri);
+                    if (mContactContentDisplayed) {
+                        setupContactDetailFragment(newContactUri);
+                    }
+
+                    mRequest.setActionCode(ContactsRequest.ACTION_VIEW_CONTACT);
+                    mRequest.setContactUri(newContactUri);
+                    mListFragment.setSelectedContactUri(newContactUri);
+                    mListFragment.saveSelectedUri(mPrefs);
+                    mListFragment.requestSelectionOnScreen(true);
                 }
                 break;
             }
diff --git a/src/com/android/contacts/activities/ContactSearchActivity.java b/src/com/android/contacts/activities/ContactSearchActivity.java
index 0c8d141..615f182 100644
--- a/src/com/android/contacts/activities/ContactSearchActivity.java
+++ b/src/com/android/contacts/activities/ContactSearchActivity.java
@@ -159,6 +159,10 @@
         public void onFinishAction() {
             onBackPressed();
         }
+
+        @Override
+        public void onInvalidSelection() {
+        }
     }
 
     private PhoneNumberInteraction getPhoneNumberCallInteraction() {
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index cfe9710..290358a 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -21,9 +21,7 @@
 import com.android.contacts.list.ContactPickerFragment;
 import com.android.contacts.list.ContactsIntentResolver;
 import com.android.contacts.list.ContactsRequest;
-import com.android.contacts.list.DefaultContactBrowseListFragment;
 import com.android.contacts.list.DirectoryListLoader;
-import com.android.contacts.list.OnContactBrowserActionListener;
 import com.android.contacts.list.OnContactPickerActionListener;
 import com.android.contacts.list.OnPhoneNumberPickerActionListener;
 import com.android.contacts.list.OnPostalAddressPickerActionListener;
@@ -59,8 +57,6 @@
 
     private int mActionCode = -1;
 
-    private boolean mSearchInitiated;
-
     private ContactsRequest mRequest;
     private SearchView mSearchView;
 
@@ -255,10 +251,7 @@
     }
 
     public void setupActionListener() {
-        if (mListFragment instanceof DefaultContactBrowseListFragment) {
-            ((DefaultContactBrowseListFragment) mListFragment).setOnContactListActionListener(
-                    new ContactBrowserActionListener());
-        } else if (mListFragment instanceof ContactPickerFragment) {
+        if (mListFragment instanceof ContactPickerFragment) {
             ((ContactPickerFragment) mListFragment).setOnContactPickerActionListener(
                     new ContactPickerActionListener());
         } else if (mListFragment instanceof PhoneNumberPickerFragment) {
@@ -272,63 +265,6 @@
         }
     }
 
-    private final class ContactBrowserActionListener implements OnContactBrowserActionListener {
-        @Override
-        public void onViewContactAction(Uri contactLookupUri) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void onCreateNewContactAction() {
-            Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
-            Bundle extras = getIntent().getExtras();
-            if (extras != null) {
-                intent.putExtras(extras);
-            }
-            startActivity(intent);
-        }
-
-        @Override
-        public void onEditContactAction(Uri contactLookupUri) {
-            Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
-            Bundle extras = getIntent().getExtras();
-            if (extras != null) {
-                intent.putExtras(extras);
-            }
-            startActivity(intent);
-        }
-
-        @Override
-        public void onAddToFavoritesAction(Uri contactUri) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void onRemoveFromFavoritesAction(Uri contactUri) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void onCallContactAction(Uri contactUri) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void onSmsContactAction(Uri contactUri) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void onDeleteContactAction(Uri contactUri) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void onFinishAction() {
-            onBackPressed();
-        }
-    }
-
     private final class ContactPickerActionListener implements OnContactPickerActionListener {
         @Override
         public void onCreateNewContactAction() {
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index f99134b..5a81f6a 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -21,6 +21,7 @@
 import android.app.LoaderManager.LoaderCallbacks;
 import android.content.CursorLoader;
 import android.content.Loader;
+import android.content.SharedPreferences;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
@@ -38,6 +39,7 @@
         ContactEntryListFragment<ContactListAdapter> {
 
     private static final String KEY_SELECTED_URI = "selectedUri";
+    private static final String KEY_SELECTION_VERIFIED = "selectionVerified";
 
     private static final int SELECTED_ID_LOADER = -3;
 
@@ -49,8 +51,10 @@
     private long mSelectedContactDirectoryId;
     private String mSelectedContactLookupKey;
     private int mSelectionVisibilityRequest;
+    private boolean mSelectionVerified;
+    private boolean mLoadingLookupKey;
 
-    private OnContactBrowserActionListener mListener;
+    protected OnContactBrowserActionListener mListener;
 
     private LoaderCallbacks<Cursor> mIdLoaderCallbacks = new LoaderCallbacks<Cursor>() {
 
@@ -66,6 +70,7 @@
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+            mLoadingLookupKey = false;
             String lookupKey = null;
             if (data != null) {
                 if (data.moveToFirst()) {
@@ -76,7 +81,6 @@
                 mSelectedContactLookupKey = lookupKey;
                 configureContactSelection();
             }
-            return;
         }
     };
 
@@ -89,6 +93,7 @@
         }
 
         mSelectedContactUri = savedState.getParcelable(KEY_SELECTED_URI);
+        mSelectionVerified = savedState.getBoolean(KEY_SELECTION_VERIFIED);
         parseSelectedContactUri();
     }
 
@@ -96,6 +101,7 @@
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         outState.putParcelable(KEY_SELECTED_URI, mSelectedContactUri);
+        outState.putBoolean(KEY_SELECTION_VERIFIED, mSelectionVerified);
     }
 
     @Override
@@ -109,6 +115,7 @@
         if (isSelectionVisible() && mSelectedContactUri != null &&
                 (mSelectedContactDirectoryId == Directory.DEFAULT ||
                         mSelectedContactDirectoryId == Directory.LOCAL_INVISIBLE)) {
+            mLoadingLookupKey = true;
             getLoaderManager().restartLoader(SELECTED_ID_LOADER, null, mIdLoaderCallbacks);
         } else {
             getLoaderManager().stopLoader(SELECTED_ID_LOADER);
@@ -165,7 +172,7 @@
             }
 
             String directoryParam =
-                mSelectedContactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
+                    mSelectedContactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
             mSelectedContactDirectoryId = TextUtils.isEmpty(directoryParam)
                     ? Directory.DEFAULT
                     : Long.parseLong(directoryParam);
@@ -193,6 +200,49 @@
         }
 
         adapter.setSelectedContact(mSelectedContactDirectoryId, mSelectedContactLookupKey);
+        checkSelection();
+    }
+
+    @Override
+    protected void onPartitionLoaded(int partitionIndex, Cursor data) {
+        super.onPartitionLoaded(partitionIndex, data);
+        checkSelection();
+    }
+
+    private void checkSelection() {
+        if (mSelectionVerified || isSearchMode()) {
+            return;
+        }
+
+        ContactListAdapter adapter = getAdapter();
+        if (adapter.isLoading() || mLoadingLookupKey) {
+            return;
+        }
+
+        if (adapter.hasValidSelection()) {
+            mSelectionVerified = true;
+            requestSelectionOnScreenIfNeeded();
+            return;
+        }
+
+        notifyInvalidSelection();
+    }
+
+    public Uri getFirstContactUri() {
+        ContactListAdapter adapter = getAdapter();
+        return adapter.getFirstContactUri();
+    }
+
+    @Override
+    protected void startLoading() {
+        mSelectionVerified = false;
+        super.startLoading();
+    }
+
+    @Override
+    public void reloadData() {
+        mSelectionVerified = false;
+        super.reloadData();
     }
 
     public void setOnContactListActionListener(OnContactBrowserActionListener listener) {
@@ -231,6 +281,10 @@
         mListener.onSmsContactAction(contactUri);
     }
 
+    private void notifyInvalidSelection() {
+        mListener.onInvalidSelection();
+    }
+
     @Override
     protected void finish() {
         super.finish();
@@ -269,4 +323,10 @@
                     listView, position + listView.getHeaderViewsCount(), smooth);
         }
     }
+
+    public void saveSelectedUri(SharedPreferences preferences) {
+    }
+
+    public void restoreSelectedUri(SharedPreferences preferences) {
+    }
 }
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index 7e2c4c2..08b3e48 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -30,7 +30,6 @@
 import android.widget.ListView;
 import android.widget.QuickContactBadge;
 
-import java.util.List;
 
 /**
  * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
@@ -105,7 +104,6 @@
     private String mSelectedContactLookupKey;
 
     private ContactListFilter mFilter;
-    private List<ContactListFilter> mAllFilters;
 
     public ContactListAdapter(Context context) {
         super(context);
@@ -118,22 +116,14 @@
     }
 
     /**
-     * Returns a full set of all available list filters.
-     */
-    public List<ContactListFilter> getAllFilters() {
-        return mAllFilters;
-    }
-
-    /**
      * Returns the currently selected filter.
      */
     public ContactListFilter getFilter() {
         return mFilter;
     }
 
-    public void setFilter(ContactListFilter filter, List<ContactListFilter> allFilters) {
+    public void setFilter(ContactListFilter filter) {
         mFilter = filter;
-        mAllFilters = allFilters;
     }
 
     public long getSelectedContactDirectoryId() {
@@ -326,4 +316,31 @@
         }
         return position;
     }
+
+    public boolean hasValidSelection() {
+        return getSelectedContactPosition() != -1;
+    }
+
+    public Uri getFirstContactUri() {
+        int partitionCount = getPartitionCount();
+        for (int i = 0; i < partitionCount; i++) {
+            DirectoryPartition partition = (DirectoryPartition) getPartition(i);
+            if (partition.isLoading()) {
+                continue;
+            }
+
+            Cursor cursor = getCursor(i);
+            if (cursor == null) {
+                continue;
+            }
+
+            if (!cursor.moveToFirst()) {
+                continue;
+            }
+
+            return getContactUri(i, cursor);
+        }
+
+        return null;
+    }
 }
diff --git a/src/com/android/contacts/list/ContactListFilter.java b/src/com/android/contacts/list/ContactListFilter.java
index 0189bbb..2b212fd 100644
--- a/src/com/android/contacts/list/ContactListFilter.java
+++ b/src/com/android/contacts/list/ContactListFilter.java
@@ -30,6 +30,7 @@
     public static final int FILTER_TYPE_CUSTOM = -3;
     public static final int FILTER_TYPE_STARRED = -4;
     public static final int FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY = -5;
+    public static final int FILTER_TYPE_SINGLE_CONTACT = -6;
 
     public static final int FILTER_TYPE_ACCOUNT = 0;
     public static final int FILTER_TYPE_GROUP = 1;
@@ -38,13 +39,16 @@
     private static final String KEY_ACCOUNT_NAME = "filter.accountName";
     private static final String KEY_ACCOUNT_TYPE = "filter.accountType";
     private static final String KEY_GROUP_ID = "filter.groupId";
+    private static final String KEY_GROUP_SOURCE_ID = "filter.groupSourceId";
 
     public int filterType;
     public String accountType;
     public String accountName;
     public Drawable icon;
     public long groupId;
+    public String groupSourceId;
     public String title;
+    private String mId;
 
     public ContactListFilter(int filterType) {
         this.filterType = filterType;
@@ -60,11 +64,12 @@
     }
 
     public ContactListFilter(
-            String accountType, String accountName, long groupId, String title) {
+            String accountType, String accountName, long groupId, String groupSourceId, String title) {
         this.filterType = ContactListFilter.FILTER_TYPE_GROUP;
         this.accountType = accountType;
         this.accountName = accountName;
         this.groupId = groupId;
+        this.groupSourceId = groupSourceId;
         this.title = title;
     }
 
@@ -115,7 +120,9 @@
             code = code * 31 + accountType.hashCode();
             code = code * 31 + accountName.hashCode();
         }
-        if (groupId != 0) {
+        if (groupSourceId != null) {
+            code = code * 31 + groupSourceId.hashCode();
+        } else if (groupId != 0) {
             code = code * 31 + (int) groupId;
         }
         return code;
@@ -132,10 +139,17 @@
         }
 
         ContactListFilter otherFilter = (ContactListFilter) other;
-        return filterType == otherFilter.filterType
-                && TextUtils.equals(accountName, otherFilter.accountName)
-                && TextUtils.equals(accountType, otherFilter.accountType)
-                && groupId == otherFilter.groupId;
+        if (filterType != otherFilter.filterType
+                || !TextUtils.equals(accountName, otherFilter.accountName)
+                || !TextUtils.equals(accountType, otherFilter.accountType)) {
+            return false;
+        }
+
+        if (groupSourceId != null && otherFilter.groupSourceId != null) {
+            return groupSourceId.equals(otherFilter.groupSourceId);
+        }
+
+        return groupId == otherFilter.groupId;
     }
 
     public static void storeToPreferences(SharedPreferences prefs, ContactListFilter filter) {
@@ -144,6 +158,7 @@
             .putString(KEY_ACCOUNT_NAME, filter == null ? null : filter.accountName)
             .putString(KEY_ACCOUNT_TYPE, filter == null ? null : filter.accountType)
             .putLong(KEY_GROUP_ID, filter == null ? -1 : filter.groupId)
+            .putString(KEY_GROUP_SOURCE_ID, filter == null ? null : filter.groupSourceId)
             .apply();
     }
 
@@ -157,6 +172,30 @@
         filter.accountName = prefs.getString(KEY_ACCOUNT_NAME, null);
         filter.accountType = prefs.getString(KEY_ACCOUNT_TYPE, null);
         filter.groupId = prefs.getLong(KEY_GROUP_ID, -1);
+        filter.groupSourceId = prefs.getString(KEY_GROUP_SOURCE_ID, null);
         return filter;
     }
+
+    /**
+     * Returns a string that can be used as a stable persistent identifier for this filter.
+     */
+    public String getId() {
+        if (mId == null) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(filterType);
+            if (accountType != null) {
+                sb.append('-').append(accountType);
+            }
+            if (accountName != null) {
+                sb.append('-').append(accountName.replace('-', '_'));
+            }
+            if (groupSourceId != null) {
+                sb.append('-').append(groupSourceId);
+            } else if (groupId != 0) {
+                sb.append('-').append(groupId);
+            }
+            mId = sb.toString();
+        }
+        return mId;
+    }
 }
diff --git a/src/com/android/contacts/list/ContactListFilterController.java b/src/com/android/contacts/list/ContactListFilterController.java
index c342d75..4b890cf 100644
--- a/src/com/android/contacts/list/ContactListFilterController.java
+++ b/src/com/android/contacts/list/ContactListFilterController.java
@@ -249,7 +249,9 @@
     public void setContactListFilter(ContactListFilter filter, boolean persistent) {
         if (!filter.equals(mFilter)) {
             mFilter = filter;
-            ContactListFilter.storeToPreferences(getSharedPreferences(), mFilter);
+            if (persistent) {
+                ContactListFilter.storeToPreferences(getSharedPreferences(), mFilter);
+            }
             if (mListeners != null) {
                notifyContactListFilterChanged();
             }
diff --git a/src/com/android/contacts/list/ContactListFilterLoader.java b/src/com/android/contacts/list/ContactListFilterLoader.java
index ae791ab..663a6a8 100644
--- a/src/com/android/contacts/list/ContactListFilterLoader.java
+++ b/src/com/android/contacts/list/ContactListFilterLoader.java
@@ -16,8 +16,8 @@
 
 package com.android.contacts.list;
 
-import com.android.contacts.model.BaseAccountType;
 import com.android.contacts.model.AccountTypes;
+import com.android.contacts.model.BaseAccountType;
 
 import android.accounts.Account;
 import android.content.AsyncTaskLoader;
@@ -29,7 +29,6 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 
 /**
@@ -44,6 +43,7 @@
             Groups.ACCOUNT_NAME,
             Groups.TITLE,
             Groups.AUTO_ADD,
+            Groups.SOURCE_ID,
         };
 
         public static final int ID = 0;
@@ -51,6 +51,7 @@
         public static final int ACCOUNT_NAME = 2;
         public static final int TITLE = 3;
         public static final int IS_DEFAULT_GROUP = 4;       // Using the AUTO_ADD group as default
+        public static final int SOURCE_ID = 5;
 
         private static final String SELECTION =
                 Groups.DELETED + "=0 AND " + Groups.FAVORITES + "=0";
@@ -74,7 +75,6 @@
             results.add(new ContactListFilter(account.type, account.name, icon, account.name));
         }
 
-        HashSet<Account> accountsWithGroups = new HashSet<Account>();
         ContentResolver resolver = context.getContentResolver();
 
         Cursor cursor = resolver.query(
@@ -82,6 +82,7 @@
         try {
             while (cursor.moveToNext()) {
                 long groupId = cursor.getLong(GroupQuery.ID);
+                String groupSourceId = cursor.getString(GroupQuery.SOURCE_ID);
                 String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
                 String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
                 boolean defaultGroup = false;
@@ -94,12 +95,14 @@
                         if (filter.accountName.equals(accountName)
                                 && filter.accountType.equals(accountType)) {
                             filter.groupId = groupId;
+                            filter.groupSourceId = groupSourceId;
                             break;
                         }
                     }
                 } else {
                     String title = cursor.getString(GroupQuery.TITLE);
-                    results.add(new ContactListFilter(accountType, accountName, groupId, title));
+                    results.add(new ContactListFilter(
+                            accountType, accountName, groupId, groupSourceId, title));
                 }
             }
         } finally {
diff --git a/src/com/android/contacts/list/ContactListFilterView.java b/src/com/android/contacts/list/ContactListFilterView.java
index e065e91..cb9ec06 100644
--- a/src/com/android/contacts/list/ContactListFilterView.java
+++ b/src/com/android/contacts/list/ContactListFilterView.java
@@ -105,6 +105,10 @@
                 bindView(0, R.string.list_filter_phones);
                 break;
             }
+            case ContactListFilter.FILTER_TYPE_SINGLE_CONTACT: {
+                bindView(0, R.string.list_filter_single);
+                break;
+            }
             case ContactListFilter.FILTER_TYPE_ACCOUNT: {
                 if (mIcon != null) {
                     mIcon.setVisibility(View.VISIBLE);
diff --git a/src/com/android/contacts/list/ContactPickerFragment.java b/src/com/android/contacts/list/ContactPickerFragment.java
index 0937608..a0c29e1 100644
--- a/src/com/android/contacts/list/ContactPickerFragment.java
+++ b/src/com/android/contacts/list/ContactPickerFragment.java
@@ -152,7 +152,7 @@
         if (!isLegacyCompatibilityMode()) {
             DefaultContactListAdapter adapter = new DefaultContactListAdapter(getActivity());
             adapter.setFilter(
-                    new ContactListFilter(ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS), null);
+                    new ContactListFilter(ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS));
             adapter.setSectionHeaderDisplayEnabled(true);
             adapter.setDisplayPhotos(true);
             adapter.setQuickContactEnabled(false);
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 651228b..7729e28 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -17,9 +17,10 @@
 
 import com.android.contacts.R;
 
-import android.app.Activity;
-import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
 import android.database.Cursor;
+import android.net.Uri;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -28,22 +29,23 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+
 /**
  * Fragment containing a contact list used for browsing (as compared to
  * picking a contact with one of the PICK intents).
  */
-public class DefaultContactBrowseListFragment extends ContactBrowseListFragment
-        implements ContactListFilterController.ContactListFilterListener {
+public class DefaultContactBrowseListFragment extends ContactBrowseListFragment {
 
     private static final String KEY_FILTER_ENABLED = "filterEnabled";
 
-    private static final int REQUEST_CODE_CUSTOMIZE_FILTER = 3;
+    private static final String PERSISTENT_SELECTION_PREFIX = "defaultContactBrowserSelection";
 
     private View mCounterHeaderView;
     private View mSearchHeaderView;
 
     private boolean mFilterEnabled;
-    private ContactListFilterController mFilterController;
+    private ContactListFilter mFilter;
+    private String mPersistentSelectionPrefix = PERSISTENT_SELECTION_PREFIX;
 
     public DefaultContactBrowseListFragment() {
         setPhotoLoaderEnabled(true);
@@ -51,17 +53,8 @@
         setAizyEnabled(true);
     }
 
-    public void setContactListFilterController(ContactListFilterController filterController) {
-        mFilterController = filterController;
-        mFilterController.addListener(this);
-    }
-
-    @Override
-    public void onDetach() {
-        if (mFilterController != null) {
-            mFilterController.removeListener(this);
-        }
-        super.onDetach();
+    public void setFilter(ContactListFilter filter) {
+        mFilter = filter;
     }
 
     @Override
@@ -99,8 +92,8 @@
         super.configureAdapter();
 
         DefaultContactListAdapter adapter = (DefaultContactListAdapter)getAdapter();
-        if (adapter != null && mFilterEnabled && mFilterController != null) {
-            adapter.setFilter(mFilterController.getFilter(), mFilterController.getFilterList());
+        if (adapter != null && mFilter != null) {
+            adapter.setFilter(mFilter);
         }
     }
 
@@ -189,47 +182,47 @@
     }
 
     @Override
-    protected void startLoading() {
-        if (mFilterController != null && !mFilterController.isLoaded()) {
-            mFilterController.startLoading();
-        }
-
-        if (!mFilterEnabled || mFilterController == null || mFilterController.isLoaded()) {
+    public void startLoading() {
+        if (!mFilterEnabled || mFilter != null) {
             super.startLoading();
         }
     }
 
     @Override
-    protected void onPartitionLoaded(int partitionIndex, Cursor data) {
-        super.onPartitionLoaded(partitionIndex, data);
-        if (mFilterController != null) {
-            mFilterController.postDelayedRefresh();
+    public void saveSelectedUri(SharedPreferences preferences) {
+        if (isSearchMode()) {
+            return;
+        }
+
+        Editor editor = preferences.edit();
+        Uri uri = getSelectedContactUri();
+        if (uri == null) {
+            editor.remove(getPersistentSelectionKey());
+        } else {
+            editor.putString(getPersistentSelectionKey(), uri.toString());
+        }
+        editor.apply();
+    }
+
+    @Override
+    public void restoreSelectedUri(SharedPreferences preferences) {
+        if (isSearchMode()) {
+            return;
+        }
+
+        String selectedUri = preferences.getString(getPersistentSelectionKey(), null);
+        if (selectedUri == null) {
+            setSelectedContactUri(null);
+        } else {
+            setSelectedContactUri(Uri.parse(selectedUri));
         }
     }
 
-    @Override
-    public void onContactListFiltersLoaded() {
-        if (mFilterEnabled) {
-            // Filters have been loaded - now we can start loading the list itself
-            startLoading();
-        }
-    }
-
-    @Override
-    public void onContactListFilterChanged() {
-        reloadData();
-    }
-
-    @Override
-    public void onContactListFilterCustomizationRequest() {
-        startActivityForResult(new Intent(getContext(), CustomContactListFilterActivity.class),
-                REQUEST_CODE_CUSTOMIZE_FILTER);
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (requestCode == REQUEST_CODE_CUSTOMIZE_FILTER && resultCode == Activity.RESULT_OK) {
-            mFilterController.selectCustomFilter();
+    private String getPersistentSelectionKey() {
+        if (mFilter == null) {
+            return mPersistentSelectionPrefix;
+        } else {
+            return mPersistentSelectionPrefix + "-" + mFilter.getId();
         }
     }
 }
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index ede3597..577e5bb 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -17,6 +17,7 @@
 
 import com.android.contacts.preference.ContactsPreferences;
 
+import android.content.ContentUris;
 import android.content.Context;
 import android.content.CursorLoader;
 import android.content.SharedPreferences;
@@ -82,11 +83,20 @@
     }
 
     protected void configureUri(CursorLoader loader, long directoryId, ContactListFilter filter) {
-        Uri uri;
-        if (filter != null && filter.groupId != 0) {
-            uri = Data.CONTENT_URI;
-        } else {
-            uri = Contacts.CONTENT_URI;
+        Uri uri = Contacts.CONTENT_URI;
+        if (filter != null) {
+            if (filter.filterType == ContactListFilter.FILTER_TYPE_GROUP ||
+                    filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) {
+                uri = Data.CONTENT_URI;
+            } else if (filter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
+                String lookupKey = getSelectedContactLookupKey();
+                if (lookupKey != null) {
+                    uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
+                } else {
+                    // Non-existent contact
+                    uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, 0);
+                }
+            }
         }
 
         if (directoryId == Directory.DEFAULT && isSectionHeaderDisplayEnabled()) {
@@ -94,7 +104,9 @@
         }
 
         // The "All accounts" filter is the same as the entire contents of Directory.DEFAULT
-        if (filter != null && filter.filterType != ContactListFilter.FILTER_TYPE_CUSTOM) {
+        if (filter != null
+                && filter.filterType != ContactListFilter.FILTER_TYPE_CUSTOM
+                && filter.filterType != ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
             uri = uri.buildUpon().appendQueryParameter(
                     ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
                     .build();
@@ -130,6 +142,11 @@
                 // filter
                 break;
             }
+            case ContactListFilter.FILTER_TYPE_SINGLE_CONTACT: {
+                // We have already added the lookup key to the URI, which takes care of this
+                // filter
+                break;
+            }
             case ContactListFilter.FILTER_TYPE_STARRED: {
                 selection.append(Contacts.STARRED + "!=0");
                 break;
diff --git a/src/com/android/contacts/list/OnContactBrowserActionListener.java b/src/com/android/contacts/list/OnContactBrowserActionListener.java
index 239f8e1..80d4838 100644
--- a/src/com/android/contacts/list/OnContactBrowserActionListener.java
+++ b/src/com/android/contacts/list/OnContactBrowserActionListener.java
@@ -68,4 +68,9 @@
      * Closes the contact browser.
      */
     void onFinishAction();
+
+    /**
+     * Invoked if the requested selected contact is not found in the list.
+     */
+    void onInvalidSelection();
 }