Merge change 25294 into eclair

* changes:
  Adjust to new ContactHeaderWidget api.
diff --git a/res/values/strings.xml b/res/values/strings.xml
index aeea931..c9d8603 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -934,7 +934,8 @@
     <string name="dialog_new_contact_account">Create contact under account</string>
 
     <string name="menu_sync_remove">Remove sync group</string>
-    <string name="menu_sync_add">Add sync group</string>
+    <string name="dialog_sync_add">Add sync group</string>
+    <string name="display_more_groups">More groups\u2026</string>
 
     <!-- List title for a special contacts group that covers all contacts that
          aren't members of any other group.  -->
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index a1ffff4..e39a7fc 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -432,7 +432,10 @@
                 mMode = MODE_PICK_POSTAL;
             } else if (ContactMethods.CONTENT_POSTAL_TYPE.equals(type)) {
                 mMode = MODE_LEGACY_PICK_POSTAL;
+            }  else if (People.CONTENT_ITEM_TYPE.equals(type)) {
+                mMode = MODE_LEGACY_PICK_OR_CREATE_PERSON;
             }
+
         } else if (Intent.ACTION_INSERT_OR_EDIT.equals(action)) {
             mMode = MODE_INSERT_OR_EDIT_CONTACT;
         } else if (Intent.ACTION_SEARCH.equals(action)) {
@@ -1046,6 +1049,10 @@
             startActivity(intent);
             finish();
         } else if (id != -1) {
+            // Subtract one if we have Create Contact at the top
+            if ((mMode & MODE_MASK_CREATE_NEW) != 0) {
+                position--;
+            }
             final Uri uri = getSelectedUri(position);
             if ((mMode & MODE_MASK_PICKER) == 0) {
                 final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
@@ -1062,10 +1069,7 @@
                     || mMode == MODE_LEGACY_PICK_PERSON
                     || mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON) {
                 if (mShortcutAction != null) {
-                    // Subtract one if we have Create Contact at the top
-                    Cursor c = (Cursor) mAdapter.getItem(position
-                            - (mMode == MODE_PICK_OR_CREATE_CONTACT
-                                    || mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON ? 1:0));
+                    Cursor c = (Cursor) mAdapter.getItem(position);
                     returnPickerResult(c, c.getString(SUMMARY_NAME_COLUMN_INDEX), uri, id);
                 } else {
                     returnPickerResult(null, null, uri, id);
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index 725c65b..c0b7634 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts;
 
+import android.accounts.Account;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.ProgressDialog;
@@ -44,6 +45,15 @@
 import android.text.Spanned;
 import android.text.style.RelativeSizeSpan;
 import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.Sources;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -139,17 +149,21 @@
         // For reading multiple files.
         private List<VCardFile> mVCardFileList;
         private List<String> mErrorFileNameList;
+
+        final private Account mAccount;
         
-        public VCardReadThread(String canonicalPath) {
+        public VCardReadThread(String canonicalPath, Account account) {
             mCanonicalPath = canonicalPath;
             mVCardFileList = null;
+            mAccount = account;
             init();
         }
 
-        public VCardReadThread(List<VCardFile> vcardFileList) {
+        public VCardReadThread(List<VCardFile> vcardFileList, Account account) {
             mCanonicalPath = null;
             mVCardFileList = vcardFileList;
             mErrorFileNameList = new ArrayList<String>();
+            mAccount = account;
             init();
         }
 
@@ -223,7 +237,8 @@
                     mProgressDialog.setIndeterminate(false);
                     mProgressDialog.setMax(counter.getCount());
                     String charset = detector.getEstimatedCharset();
-                    doActuallyReadOneVCard(mCanonicalPath, charset, true, detector, null);
+                    doActuallyReadOneVCard(mCanonicalPath, null, charset, true, detector,
+                            mErrorFileNameList);
                 } else {  // Read multiple files.
                     mProgressDialog.setProgressNumberFormat(
                             getString(R.string.reading_vcard_files));
@@ -246,7 +261,7 @@
                             // Assume that VCardSourceDetector was able to detect the source.
                         }
                         String charset = detector.getEstimatedCharset();
-                        doActuallyReadOneVCard(canonicalPath,
+                        doActuallyReadOneVCard(canonicalPath, mAccount,
                                 charset, false, detector, mErrorFileNameList);
                         mProgressDialog.incrementProgressBy(1);
                     }
@@ -278,7 +293,7 @@
             }
         }
 
-        private boolean doActuallyReadOneVCard(String canonicalPath,
+        private boolean doActuallyReadOneVCard(String canonicalPath, Account account,
                 String charset, boolean showEntryParseProgress,
                 VCardSourceDetector detector, List<String> errorFileNameList) {
             final Context context = ImportVCardActivity.this;
@@ -287,10 +302,10 @@
             int vcardType = VCardConfig.getVCardTypeFromString(
                     context.getString(R.string.config_import_vcard_type));
             if (charset != null) {
-                builder = new VCardDataBuilder(charset, charset, false, vcardType);
+                builder = new VCardDataBuilder(charset, charset, false, vcardType, mAccount);
             } else {
                 charset = VCardConfig.DEFAULT_CHARSET;
-                builder = new VCardDataBuilder(null, null, false, vcardType);
+                builder = new VCardDataBuilder(null, null, false, vcardType, mAccount);
             }
             builder.addEntryHandler(new EntryCommitter(mResolver));
             if (showEntryParseProgress) {
@@ -395,24 +410,26 @@
         public static final int IMPORT_ALL = 2;
         public static final int IMPORT_TYPE_SIZE = 3;
         
-        private List<VCardFile> mVCardFileList;
+        final private List<VCardFile> mVCardFileList;
+        final private Account mAccount;
         private int mCurrentIndex;
 
-        public ImportTypeSelectedListener(List<VCardFile> vcardFileList) {
+        public ImportTypeSelectedListener(List<VCardFile> vcardFileList, Account account) {
             mVCardFileList = vcardFileList;
+            mAccount = account;
         }
 
         public void onClick(DialogInterface dialog, int which) {
             if (which == DialogInterface.BUTTON_POSITIVE) {
                 switch (mCurrentIndex) {
                 case IMPORT_ALL:
-                    importMultipleVCardFromSDCard(mVCardFileList);
+                    importMultipleVCardFromSDCard(mVCardFileList, mAccount);
                     break;
                 case IMPORT_MULTIPLE:
-                    showVCardFileSelectDialog(mVCardFileList, true);
+                    showVCardFileSelectDialog(mVCardFileList, mAccount, true);
                     break;
                 default:
-                    showVCardFileSelectDialog(mVCardFileList, false);
+                    showVCardFileSelectDialog(mVCardFileList, mAccount, false);
                 }
             } else if (which == DialogInterface.BUTTON_NEGATIVE) {
                 finish();
@@ -422,16 +439,43 @@
         }
     }
 
+    private class AccountSelectedListener
+            implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+
+        final private List<Account> mAccountList;
+        final private List<VCardFile> mVCardFileList;
+
+        public AccountSelectedListener(List<Account> accountList, List<VCardFile> vcardFileList) {
+            if (accountList == null || accountList.size() == 0) {
+                Log.e(LOG_TAG, "The size of Account list is 0");
+            }
+            mAccountList = accountList;
+            mVCardFileList = vcardFileList;
+        }
+
+        public void onClick(DialogInterface dialog, int which) {
+            dialog.dismiss();
+            startVCardSelectAndImport(mVCardFileList, mAccountList.get(which));
+        }
+
+        public void onCancel(DialogInterface dialog) {
+            finish();
+        }
+    }
     
     private class VCardSelectedListener implements
             DialogInterface.OnClickListener, DialogInterface.OnMultiChoiceClickListener {
-        private List<VCardFile> mVCardFileList;
+        final private List<VCardFile> mVCardFileList;
+        final private Account mAccount;
         private int mCurrentIndex;
         private Set<Integer> mSelectedIndexSet;
 
         public VCardSelectedListener(
-                List<VCardFile> vcardFileList, boolean multipleSelect) {
+                List<VCardFile> vcardFileList,
+                Account account,
+                boolean multipleSelect) {
             mVCardFileList = vcardFileList;
+            mAccount = account;
             mCurrentIndex = 0;
             if (multipleSelect) {
                 mSelectedIndexSet = new HashSet<Integer>();
@@ -449,9 +493,10 @@
                             selectedVCardFileList.add(mVCardFileList.get(i));
                         }
                     }
-                    importMultipleVCardFromSDCard(selectedVCardFileList);
+                    importMultipleVCardFromSDCard(selectedVCardFileList, mAccount);
                 } else {
-                    importOneVCardFromSDCard(mVCardFileList.get(mCurrentIndex).getCanonicalPath());
+                    importOneVCardFromSDCard(mVCardFileList.get(mCurrentIndex).getCanonicalPath(),
+                            mAccount);
                 }
             } else if (which == DialogInterface.BUTTON_NEGATIVE) {
                 finish();
@@ -488,7 +533,7 @@
         private File mRootDirectory;
 
         // null when search operation is canceled.
-        private List<VCardFile> mVCardFiles;
+        private List<VCardFile> mVCardFileList;
 
         // To avoid recursive link.
         private Set<String> mCheckedPaths;
@@ -502,7 +547,7 @@
             mGotIOException = false;
             mRootDirectory = sdcardDirectory;
             mCheckedPaths = new HashSet<String>();
-            mVCardFiles = new Vector<VCardFile>();
+            mVCardFileList = new Vector<VCardFile>();
             PowerManager powerManager = (PowerManager)ImportVCardActivity.this.getSystemService(
                     Context.POWER_SERVICE);
             mWakeLock = powerManager.newWakeLock(
@@ -524,7 +569,7 @@
             }
 
             if (mCanceled) {
-                mVCardFiles = null;
+                mVCardFileList = null;
             }
 
             mProgressDialog.dismiss();
@@ -548,9 +593,10 @@
             } else if (mCanceled) {
                 finish();
             } else {
+                // TODO: too many nest. Clean up this code...
                 mHandler.post(new Runnable() {
                     public void run() {
-                        int size = mVCardFiles.size();
+                        int size = mVCardFileList.size();
                         final Context context = ImportVCardActivity.this;
                         if (size == 0) {
                             String message = (getString(R.string.scanning_sdcard_failed_message,
@@ -564,17 +610,59 @@
                                     .setPositiveButton(android.R.string.ok, mCancelListener);
                             builder.show();
                             return;
-                        } else if (context.getResources().getBoolean(
-                                R.bool.config_import_all_vcard_from_sdcard_automatically)) {
-                            importMultipleVCardFromSDCard(mVCardFiles);
-                        } else if (size == 1) {
-                            importOneVCardFromSDCard(mVCardFiles.get(0).getCanonicalPath());
-                        } else if (context.getResources().getBoolean(
-                                R.bool.config_allow_users_select_all_vcard_import)) {
-                            showSelectImportTypeDialog(mVCardFiles);
                         } else {
-                            // Let a user to select one vCard file.
-                            showVCardFileSelectDialog(mVCardFiles, false);
+                            final Sources sources = Sources.getInstance(context);
+                            final List<Account> accountList = sources.getAccounts(true);
+                            if (accountList == null || accountList.size() == 0) {
+                                startVCardSelectAndImport(mVCardFileList, null);
+                            } else {
+                                // A lot of codes are copied from EditContactActivity.
+                                // TODO: Can we share the logic?
+
+                                // Wrap our context to inflate list items using correct theme
+                                final Context dialogContext = new ContextThemeWrapper(
+                                        context, android.R.style.Theme_Light);
+                                final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
+                                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                                final ArrayAdapter<Account> accountAdapter =
+                                    new ArrayAdapter<Account>(context,
+                                        android.R.layout.simple_list_item_2, accountList) {
+                                    @Override
+                                    public View getView(int position, View convertView,
+                                            ViewGroup parent) {
+                                        if (convertView == null) {
+                                            convertView = dialogInflater.inflate(
+                                                    android.R.layout.simple_list_item_2,
+                                                    parent, false);
+                                        }
+
+                                        // TODO: show icon along with title
+                                        final TextView text1 =
+                                            (TextView)convertView.findViewById(android.R.id.text1);
+                                        final TextView text2 =
+                                            (TextView)convertView.findViewById(android.R.id.text2);
+
+                                        final Account account = this.getItem(position);
+                                        final ContactsSource source =
+                                            sources.getInflatedSource(account.type,
+                                                    ContactsSource.LEVEL_SUMMARY);
+
+                                        text1.setText(source.getDisplayLabel(context));
+                                        text2.setText(account.name);
+
+                                        return convertView;
+                                    }
+                                };
+
+                                AccountSelectedListener listener =
+                                    new AccountSelectedListener(accountList, mVCardFileList);
+                                final AlertDialog.Builder builder =
+                                    new AlertDialog.Builder(context)
+                                        .setTitle(R.string.dialog_new_contact_account)
+                                        .setSingleChoiceItems(accountAdapter, 0, listener)
+                                        .setOnCancelListener(listener);
+                                builder.show();
+                            }
                         }
                     }
                 });
@@ -605,7 +693,7 @@
                     String fileName = file.getName();
                     VCardFile vcardFile = new VCardFile(
                             fileName, canonicalPath, file.lastModified());
-                    mVCardFiles.add(vcardFile);
+                    mVCardFileList.add(vcardFile);
                 }
             }
         }
@@ -621,22 +709,40 @@
         }
     }
 
+    private void startVCardSelectAndImport(final List<VCardFile> vcardFileList,
+            final Account account) {
+        final Context context = ImportVCardActivity.this;
+        final int size = vcardFileList.size();
+        if (context.getResources().getBoolean(
+                R.bool.config_import_all_vcard_from_sdcard_automatically)) {
+            importMultipleVCardFromSDCard(vcardFileList, account);
+        } else if (size == 1) {
+            importOneVCardFromSDCard(vcardFileList.get(0).getCanonicalPath(), account);
+        } else if (context.getResources().getBoolean(
+                R.bool.config_allow_users_select_all_vcard_import)) {
+            showSelectImportTypeDialog(vcardFileList, account);
+        } else {
+            // Let a user to select one vCard file.
+            showVCardFileSelectDialog(vcardFileList, account, false);
+        }
+    }
     
-    private void importOneVCardFromSDCard(final String canonicalPath) {
-        VCardReadThread thread = new VCardReadThread(canonicalPath);
+    private void importOneVCardFromSDCard(final String canonicalPath, Account account) {
+        VCardReadThread thread = new VCardReadThread(canonicalPath, account);
         showReadingVCardDialog(thread);
         thread.start();
     }
 
-    private void importMultipleVCardFromSDCard(final List<VCardFile> vcardFileList) {
-        VCardReadThread thread = new VCardReadThread(vcardFileList);
+    private void importMultipleVCardFromSDCard(final List<VCardFile> vcardFileList,
+            final Account account) {
+        VCardReadThread thread = new VCardReadThread(vcardFileList, account);
         showReadingVCardDialog(thread);
         thread.start();
     }
 
-    private void showSelectImportTypeDialog(List<VCardFile> vcardFileList) {
+    private void showSelectImportTypeDialog(List<VCardFile> vcardFileList, Account account) {
         DialogInterface.OnClickListener listener =
-            new ImportTypeSelectedListener(vcardFileList);
+            new ImportTypeSelectedListener(vcardFileList, account);
         AlertDialog.Builder builder =
             new AlertDialog.Builder(ImportVCardActivity.this)
                 .setTitle(R.string.select_vcard_title)
@@ -657,10 +763,10 @@
     }
 
     private void showVCardFileSelectDialog(
-            List<VCardFile> vcardFileList, boolean multipleSelect) {
+            List<VCardFile> vcardFileList, Account account, boolean multipleSelect) {
         int size = vcardFileList.size();
         VCardSelectedListener listener =
-            new VCardSelectedListener(vcardFileList, multipleSelect);
+            new VCardSelectedListener(vcardFileList, account, multipleSelect);
         AlertDialog.Builder builder =
             new AlertDialog.Builder(this)
                 .setTitle(R.string.select_vcard_title)
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index b941b38..003e641 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -278,7 +278,7 @@
             final EditType type = iterator.next();
             final int count = typeCount.get(type.rawValue);
 
-            if (count == exactValue) {
+            if (exactValue == type.rawValue) {
                 // Found exact value match
                 return type;
             }
@@ -468,13 +468,34 @@
 
         {
             // Im
-            // TODO: handle decodeImProtocol for legacy reasons
             final DataKind kind = source.getKindForMimetype(Im.CONTENT_ITEM_TYPE);
+            fixupLegacyImType(extras);
             parseExtras(state, kind, extras, Insert.IM_PROTOCOL, Insert.IM_HANDLE, Im.DATA);
         }
     }
 
     /**
+     * Attempt to parse legacy {@link Insert#IM_PROTOCOL} values, replacing them
+     * with updated values.
+     */
+    private static void fixupLegacyImType(Bundle bundle) {
+        final String encodedString = bundle.getString(Insert.IM_PROTOCOL);
+        if (encodedString == null) return;
+
+        try {
+            final Object protocol = android.provider.Contacts.ContactMethods
+                    .decodeImProtocol(encodedString);
+            if (protocol instanceof Integer) {
+                bundle.putInt(Insert.IM_PROTOCOL, (Integer)protocol);
+            } else {
+                bundle.putString(Insert.IM_PROTOCOL, (String)protocol);
+            }
+        } catch (IllegalArgumentException e) {
+            // Ignore exception when legacy parser fails
+        }
+    }
+
+    /**
      * Parse a specific entry from the given {@link Bundle} and insert into the
      * given {@link EntityDelta}. Silently skips the insert when missing value
      * or no valid {@link EditType} found.
@@ -506,8 +527,8 @@
 
         if (editType != null && editType.customColumn != null) {
             // Write down label when custom type picked
-            final CharSequence customType = extras.getCharSequence(typeExtra);
-            child.put(editType.customColumn, customType.toString());
+            final String customType = extras.getString(typeExtra);
+            child.put(editType.customColumn, customType);
         }
     }
 }
diff --git a/src/com/android/contacts/model/EntitySet.java b/src/com/android/contacts/model/EntitySet.java
index b9a71e7..18afd2b 100644
--- a/src/com/android/contacts/model/EntitySet.java
+++ b/src/com/android/contacts/model/EntitySet.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.model;
 
+import com.android.contacts.model.EntityDelta.ValuesDelta;
 import com.google.android.collect.Lists;
 
 import android.content.ContentProviderOperation;
@@ -173,18 +174,20 @@
      * Find {@link RawContacts#_ID} of the requested {@link EntityDelta}.
      */
     public long getRawContactId(int index) {
-        if (index >=0 && index < this.size()) {
+        if (index >= 0 && index < this.size()) {
             final EntityDelta delta = this.get(index);
-            return delta.getValues().getAsLong(RawContacts._ID);
-        } else {
-            return 0;
+            final ValuesDelta values = delta.getValues();
+            if (values.isVisible()) {
+                return values.getAsLong(RawContacts._ID);
+            }
         }
+        return 0;
     }
 
     /**
      * Find index of given {@link RawContacts#_ID} when present.
      */
-    public int indexOfRawContactId(long rawContactId) {
+    public int indexOfRawContactId(Long rawContactId) {
         final int size = this.size();
         for (int i = 0; i < size; i++) {
             final long currentId = getRawContactId(i);
diff --git a/src/com/android/contacts/ui/DisplayGroupsActivity.java b/src/com/android/contacts/ui/DisplayGroupsActivity.java
index c358609..ffe423f 100644
--- a/src/com/android/contacts/ui/DisplayGroupsActivity.java
+++ b/src/com/android/contacts/ui/DisplayGroupsActivity.java
@@ -71,6 +71,9 @@
     private static final String TAG = "DisplayGroupsActivity";
 
     private static final int UNGROUPED_ID = -2;
+    private static final int UNSYNCED_ID = -3;
+
+    private static final int FOOTER_ENTRY = -4;
 
     public interface Prefs {
         public static final String DISPLAY_ONLY_PHONES = "only_phones";
@@ -251,13 +254,7 @@
         final ContentValues values = new ContentValues();
 
         // TODO: heavy update, perhaps push to background query
-        if (id != UNGROUPED_ID) {
-            // Handle persisting for normal group
-            values.put(Groups.GROUP_VISIBLE, checkbox.isChecked() ? 1 : 0);
-
-            final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, id);
-            final int count = resolver.update(groupUri, values, null, null);
-        } else {
+        if (id == UNGROUPED_ID) {
             // Handle persisting for ungrouped through Settings
             values.put(Settings.UNGROUPED_VISIBLE, checkbox.isChecked() ? 1 : 0);
 
@@ -267,6 +264,15 @@
                     settings.getString(SettingsQuery.ACCOUNT_NAME),
                     settings.getString(SettingsQuery.ACCOUNT_TYPE)
             });
+        } else if (id == UNSYNCED_ID) {
+            // Open context menu for bringing back unsynced
+            this.openContextMenu(v);
+        } else {
+            // Handle persisting for normal group
+            values.put(Groups.GROUP_VISIBLE, checkbox.isChecked() ? 1 : 0);
+
+            final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, id);
+            final int count = resolver.update(groupUri, values, null, null);
         }
 
         return true;
@@ -300,19 +306,23 @@
         final String accountType = groupCursor.getString(SettingsQuery.ACCOUNT_TYPE);
         final Account account = new Account(accountName, accountType);
 
-        if (childPosition == -1) {
+        final boolean shouldSyncUngrouped = groupCursor.getInt(SettingsQuery.SHOULD_SYNC) != 0;
+        final boolean anyUnsynced = groupCursor.getInt(SettingsQuery.ANY_UNSYNCED) != 0;
+        final boolean lastChild = (childPosition == (mAdapter.getChildrenCount(groupPosition) - 1));
+
+        if (anyUnsynced && lastChild) {
             // Show add dialog for this overall source
             showAddSync(menu, groupCursor, account, syncMode);
 
-        } else {
+        } else if (childPosition != -1) {
             // Show remove dialog for this specific group
             final Cursor childCursor = mAdapter.getChild(groupPosition, childPosition);
-            showRemoveSync(menu, account, childCursor, syncMode);
+            showRemoveSync(menu, account, childCursor, syncMode, shouldSyncUngrouped);
         }
     }
 
     protected void showRemoveSync(ContextMenu menu, final Account account, Cursor childCursor,
-            final int syncMode) {
+            final int syncMode, final boolean shouldSyncUngrouped) {
         final long groupId = childCursor.getLong(GroupsQuery._ID);
         final CharSequence title = getGroupTitle(this, childCursor);
 
@@ -320,19 +330,20 @@
         menu.add(R.string.menu_sync_remove).setOnMenuItemClickListener(
                 new OnMenuItemClickListener() {
                     public boolean onMenuItemClick(MenuItem item) {
-                        handleRemoveSync(groupId, account, syncMode, title);
+                        handleRemoveSync(groupId, account, syncMode, title, shouldSyncUngrouped);
                         return true;
                     }
                 });
     }
 
     protected void handleRemoveSync(final long groupId, final Account account, final int syncMode,
-            CharSequence title) {
-        if (syncMode == SYNC_MODE_EVERYTHING && groupId != UNGROUPED_ID) {
+            CharSequence title, boolean shouldSyncUngrouped) {
+        if (syncMode == SYNC_MODE_EVERYTHING && groupId != UNGROUPED_ID && shouldSyncUngrouped) {
             // Warn before removing this group when it would cause ungrouped to stop syncing
             final AlertDialog.Builder builder = new AlertDialog.Builder(this);
             final CharSequence removeMessage = this.getString(
                     R.string.display_warn_remove_ungrouped, title);
+            builder.setTitle(R.string.menu_sync_remove);
             builder.setMessage(removeMessage);
             builder.setNegativeButton(android.R.string.cancel, null);
             builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@@ -348,8 +359,9 @@
         }
     }
 
-    protected void showAddSync(ContextMenu menu, Cursor groupCursor, final Account account, final int syncMode) {
-        menu.setHeaderTitle(R.string.menu_sync_add);
+    protected void showAddSync(ContextMenu menu, Cursor groupCursor, final Account account,
+            final int syncMode) {
+        menu.setHeaderTitle(R.string.dialog_sync_add);
 
         // Create single "Ungrouped" item when not synced
         final boolean ungroupedAvailable = groupCursor.getInt(SettingsQuery.SHOULD_SYNC) == 0;
@@ -366,7 +378,11 @@
 
         // Create item for each available, unsynced group
         final Cursor availableGroups = this.managedQuery(Groups.CONTENT_SUMMARY_URI,
-                GroupsQuery.PROJECTION, Groups.SHOULD_SYNC + "=0", null);
+                GroupsQuery.PROJECTION, Groups.SHOULD_SYNC + "=0 AND " + Groups.ACCOUNT_NAME
+                        + "=? AND " + Groups.ACCOUNT_TYPE + "=?", new String[] {
+                        groupCursor.getString(SettingsQuery.ACCOUNT_NAME),
+                        groupCursor.getString(SettingsQuery.ACCOUNT_TYPE)
+                }, null);
         while (availableGroups.moveToNext()) {
             // Create item this unsynced group
             final long groupId = availableGroups.getLong(GroupsQuery._ID);
@@ -538,6 +554,98 @@
     }
 
     /**
+     * Special {@link Cursor} that shows zero or one items based on
+     * {@link Settings#ANY_UNSYNCED} value.
+     */
+    private static class FooterCursor extends AbstractCursor {
+        private Context mContext;
+        private Cursor mCursor;
+        private int mPosition;
+
+        public FooterCursor(Context context, Cursor cursor, int position) {
+            mContext = context;
+            mCursor = cursor;
+            mPosition = position;
+        }
+
+        @Override
+        public int getCount() {
+            assertParent();
+
+            final boolean anyUnsynced = mCursor.getInt(SettingsQuery.ANY_UNSYNCED) != 0;
+            return anyUnsynced ? 1 : 0;
+        }
+
+        @Override
+        public String[] getColumnNames() {
+            return GroupsQuery.PROJECTION;
+        }
+
+        protected void assertParent() {
+            mCursor.moveToPosition(mPosition);
+        }
+
+        @Override
+        public String getString(int column) {
+            assertParent();
+            switch(column) {
+                case GroupsQuery.ACCOUNT_NAME:
+                    return mCursor.getString(SettingsQuery.ACCOUNT_NAME);
+                case GroupsQuery.ACCOUNT_TYPE:
+                    return mCursor.getString(SettingsQuery.ACCOUNT_TYPE);
+                case GroupsQuery.TITLE:
+                    return null;
+                case GroupsQuery.RES_PACKAGE:
+                    return mContext.getPackageName();
+                case GroupsQuery.TITLE_RES:
+                    return Integer.toString(UNSYNCED_ID);
+            }
+            throw new IllegalArgumentException("Requested column not available as string");
+        }
+
+        @Override
+        public short getShort(int column) {
+            throw new IllegalArgumentException("Requested column not available as short");
+        }
+
+        @Override
+        public int getInt(int column) {
+            assertParent();
+            switch(column) {
+                case GroupsQuery._ID:
+                    return UNSYNCED_ID;
+                case GroupsQuery.TITLE_RES:
+                    return R.string.display_more_groups;
+                case GroupsQuery.GROUP_VISIBLE:
+                case GroupsQuery.SUMMARY_COUNT:
+                case GroupsQuery.SUMMARY_WITH_PHONES:
+                    return FOOTER_ENTRY;
+            }
+            throw new IllegalArgumentException("Requested column not available as int");
+        }
+
+        @Override
+        public long getLong(int column) {
+            return getInt(column);
+        }
+
+        @Override
+        public float getFloat(int column) {
+            throw new IllegalArgumentException("Requested column not available as float");
+        }
+
+        @Override
+        public double getDouble(int column) {
+            throw new IllegalArgumentException("Requested column not available as double");
+        }
+
+        @Override
+        public boolean isNull(int column) {
+            return getString(column) == null;
+        }
+    }
+
+    /**
      * Adapter that shows all display groups as returned by a {@link Cursor}
      * over {@link Groups#CONTENT_SUMMARY_URI}, along with their current visible
      * status. Splits groups into sections based on {@link Account}.
@@ -600,13 +708,14 @@
 
             final int position = groupCursor.getPosition();
             final Cursor ungroupedCursor = new HeaderCursor(mContext, groupCursor, position);
+            final Cursor unsyncedCursor = new FooterCursor(mContext, groupCursor, position);
 
             final ContentResolver resolver = mContext.getContentResolver();
             final Cursor groupsCursor = resolver.query(Groups.CONTENT_SUMMARY_URI,
                     GroupsQuery.PROJECTION, selection, selectionArgs, null);
             mActivity.startManagingCursor(groupsCursor);
 
-            return new MergeCursor(new Cursor[] { ungroupedCursor, groupsCursor });
+            return new MergeCursor(new Cursor[] { ungroupedCursor, groupsCursor, unsyncedCursor });
         }
 
         @Override
@@ -634,6 +743,12 @@
             text1.setText(title);
 //            text2.setText(descrip);
             checkbox.setChecked((membersVisible == 1));
+
+            // Hide extra views when recycled as footer
+            final boolean footerView = membersVisible == FOOTER_ENTRY;
+//            text2.setVisibility(footerView ? View.GONE : View.VISIBLE);
+            text2.setVisibility(View.GONE);
+            checkbox.setVisibility(footerView ? View.GONE : View.VISIBLE);
         }
     }
 
@@ -643,6 +758,7 @@
                 Settings.ACCOUNT_TYPE,
                 Settings.SHOULD_SYNC,
                 Settings.UNGROUPED_VISIBLE,
+                Settings.ANY_UNSYNCED,
 //                Settings.UNGROUPED_COUNT,
 //                Settings.UNGROUPED_WITH_PHONES,
         };
@@ -651,8 +767,9 @@
         final int ACCOUNT_TYPE = 1;
         final int SHOULD_SYNC = 2;
         final int UNGROUPED_VISIBLE = 3;
-//        final int UNGROUPED_COUNT = 4;
-//        final int UNGROUPED_WITH_PHONES = 5;
+        final int ANY_UNSYNCED = 4;
+//        final int UNGROUPED_COUNT = 5;
+//        final int UNGROUPED_WITH_PHONES = 6;
     }
 
     private interface GroupsQuery {
@@ -662,10 +779,10 @@
             Groups.RES_PACKAGE,
             Groups.TITLE_RES,
             Groups.GROUP_VISIBLE,
-//            Groups.SUMMARY_COUNT,
-//            Groups.SUMMARY_WITH_PHONES,
             Groups.ACCOUNT_NAME,
             Groups.ACCOUNT_TYPE,
+//            Groups.SUMMARY_COUNT,
+//            Groups.SUMMARY_WITH_PHONES,
         };
 
         final int _ID = 0;
@@ -673,9 +790,9 @@
         final int RES_PACKAGE = 2;
         final int TITLE_RES = 3;
         final int GROUP_VISIBLE = 4;
-//        final int SUMMARY_COUNT = 5;
-//        final int SUMMARY_WITH_PHONES = 6;
         final int ACCOUNT_NAME = 5;
         final int ACCOUNT_TYPE = 6;
+        final int SUMMARY_COUNT = 7;
+        final int SUMMARY_WITH_PHONES = 8;
     }
 }
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index 2354a7f..27ed008 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -61,6 +61,7 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.Contacts.Data;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -101,6 +102,8 @@
 
     private EntitySet mState;
 
+    private ArrayList<Dialog> mManagedDialogs = Lists.newArrayList();
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -129,11 +132,14 @@
         findViewById(R.id.btn_done).setOnClickListener(this);
         findViewById(R.id.btn_discard).setOnClickListener(this);
 
-        if (Intent.ACTION_EDIT.equals(action) && icicle == null) {
+        // Handle initial actions only when existing state missing
+        final boolean hasIncomingState = icicle != null && icicle.containsKey(KEY_EDIT_STATE);
+
+        if (Intent.ACTION_EDIT.equals(action) && !hasIncomingState) {
             // Read initial state from database
             new QueryEntitiesTask(this).execute(intent);
 
-        } else if (Intent.ACTION_INSERT.equals(action) && icicle == null) {
+        } else if (Intent.ACTION_INSERT.equals(action) && !hasIncomingState) {
             // Trigger dialog to pick account type
             doAddAction();
         }
@@ -180,7 +186,7 @@
 
             // Handle any incoming values that should be inserted
             final Bundle extras = intent.getExtras();
-            final boolean hasExtras = extras.size() > 0;
+            final boolean hasExtras = extras != null && extras.size() > 0;
             final boolean hasState = target.mState.size() > 0;
             if (hasExtras && hasState) {
                 // Find source defining the first RawContact found
@@ -224,7 +230,7 @@
         bindHeader();
 
         if (hasValidState()) {
-            final long selectedId = savedInstanceState.getLong(KEY_SELECTED_RAW_CONTACT);
+            final Long selectedId = savedInstanceState.getLong(KEY_SELECTED_RAW_CONTACT);
             setSelectedRawContactId(selectedId);
         }
 
@@ -232,20 +238,68 @@
         super.onRestoreInstanceState(savedInstanceState);
     }
 
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        for (Dialog dialog : mManagedDialogs) {
+            if (dialog.isShowing()) {
+                dialog.dismiss();
+            }
+        }
+    }
+
+    /**
+     * Start managing this {@link Dialog} along with the {@link Activity}.
+     */
+    private void startManagingDialog(Dialog dialog) {
+        synchronized (mManagedDialogs) {
+            mManagedDialogs.add(dialog);
+        }
+    }
+
+    /**
+     * Show this {@link Dialog} and manage with the {@link Activity}.
+     */
+    private void showAndManageDialog(Dialog dialog) {
+        startManagingDialog(dialog);
+        dialog.show();
+    }
+
     /**
      * Return the {@link RawContacts#_ID} of the currently selected tab.
      */
-    protected long getSelectedRawContactId() {
-        final int index = mTabWidget.getCurrentTab();
-        return mState.getRawContactId(index);
+    protected Long getSelectedRawContactId() {
+        final int tabIndex = mTabWidget.getCurrentTab();
+        return this.mTabRawContacts.get(tabIndex);
+    }
+
+    /**
+     * Return the {@link EntityDelta} for the currently selected tab.
+     */
+    protected EntityDelta getSelectedEntityDelta() {
+        final Long rawContactId = getSelectedRawContactId();
+        final int stateIndex = mState.indexOfRawContactId(rawContactId);
+        return mState.get(stateIndex);
     }
 
     /**
      * Set the selected tab based on the given {@link RawContacts#_ID}.
      */
-    protected void setSelectedRawContactId(long rawContactId) {
-        final int index = mState.indexOfRawContactId(rawContactId);
-        mTabWidget.setCurrentTab(index);
+    protected void setSelectedRawContactId(Long rawContactId) {
+        int tabIndex = 0;
+
+        // Find index of requested contact
+        final int size = mTabRawContacts.size();
+        for (int i = 0; i < size; i++) {
+            if (mTabRawContacts.valueAt(i) == rawContactId) {
+                tabIndex = i;
+                break;
+            }
+        }
+
+        mTabWidget.setCurrentTab(tabIndex);
+        this.onTabSelectionChanged(tabIndex, false);
     }
 
     /**
@@ -259,6 +313,13 @@
 
 
     /**
+     * Map from {@link #mTabWidget} indexes to {@link RawContacts#_ID}, usually
+     * used when mapping to {@link #mState}.
+     */
+    private SparseArray<Long> mTabRawContacts = new SparseArray<Long>();
+
+
+    /**
      * Rebuild tabs to match our underlying {@link #mState} object, usually
      * called once we've parsed {@link Entity} data or have inserted a new
      * {@link RawContacts}.
@@ -267,24 +328,34 @@
         if (!hasValidState()) return;
 
         final Sources sources = Sources.getInstance(this);
-        int selectedTab = 0;
+        final Long selectedRawContactId = this.getSelectedRawContactId();
 
+        // Remove any existing tabs and rebuild any visible
         mTabWidget.removeAllTabs();
+        mTabRawContacts.clear();
         for (EntityDelta entity : mState) {
-            ValuesDelta values = entity.getValues();
+            final ValuesDelta values = entity.getValues();
+            if (!values.isVisible()) continue;
+
             final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
             final Long rawContactId = values.getAsLong(RawContacts._ID);
             final ContactsSource source = sources.getInflatedSource(accountType,
                     ContactsSource.LEVEL_CONSTRAINTS);
 
+            final int tabIndex = mTabWidget.getTabCount();
             final View tabView = ContactsUtils.createTabIndicatorView(
                     mTabWidget.getTabParent(), source);
             mTabWidget.addTab(tabView);
+            mTabRawContacts.put(tabIndex, rawContactId);
         }
 
-        if (mState.size() > 0) {
-            mTabWidget.setCurrentTab(selectedTab);
-            this.onTabSelectionChanged(selectedTab, false);
+        final boolean hasActiveTabs = mTabWidget.getTabCount() > 0;
+        if (hasActiveTabs) {
+            // Focus on last selected contact
+            this.setSelectedRawContactId(selectedRawContactId);
+        } else {
+            // Nothing remains to edit, save and bail entirely
+            this.doSaveAction();
         }
 
         // Show editor now that we've loaded state
@@ -317,10 +388,9 @@
     /** {@inheritDoc} */
     public void onTabSelectionChanged(int tabIndex, boolean clicked) {
         if (!hasValidState()) return;
-        if (tabIndex < 0 || tabIndex >= mState.size()) return;
 
         // Find entity and source for selected tab
-        final EntityDelta entity = mState.get(tabIndex);
+        final EntityDelta entity = this.getSelectedEntityDelta();
         final String accountType = entity.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
 
         final Sources sources = Sources.getInstance(this);
@@ -334,13 +404,13 @@
     /** {@inheritDoc} */
     public void onDisplayNameLongClick(View view) {
         if (!hasValidState()) return;
-        this.createNameDialog().show();
+        showAndManageDialog(createNameDialog());
     }
 
     /** {@inheritDoc} */
     public void onPhotoLongClick(View view) {
         if (!hasValidState()) return;
-        this.createPhotoDialog().show();
+        showAndManageDialog(createPhotoDialog());
     }
 
 
@@ -535,11 +605,10 @@
         if (!hasValidState()) return false;
 
         // Pass back last-selected contact
-        final int selectedTab = mTabWidget.getCurrentTab();
-        final long rawContactId = mState.getRawContactId(selectedTab);
-        if (rawContactId != -1) {
+        final Long rawContactId = this.getSelectedRawContactId();
+        if (rawContactId != null) {
             final Intent intent = new Intent();
-            intent.putExtra(ViewContactActivity.RAW_CONTACT_ID_EXTRA, rawContactId);
+            intent.putExtra(ViewContactActivity.RAW_CONTACT_ID_EXTRA, (long)rawContactId);
             setResult(RESULT_OK, intent);
         }
 
@@ -586,7 +655,7 @@
     private boolean doDeleteAction() {
         if (!hasValidState()) return false;
 
-        this.createDeleteDialog().show();
+        showAndManageDialog(createDeleteDialog());
         return true;
     }
 
@@ -745,7 +814,7 @@
 
         @Override
         protected void onPostExecute(EditContactActivity target, AlertDialog.Builder result) {
-            result.create().show();
+            target.showAndManageDialog(result.create());
         }
     }
 
@@ -759,11 +828,9 @@
         builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
             public void onClick(DialogInterface dialog, int which) {
                 // Mark the currently selected contact for deletion
-                final int index = mTabWidget.getCurrentTab();
-                final EntityDelta delta = mState.get(index);
+                final EntityDelta delta = getSelectedEntityDelta();
                 delta.markDeleted();
 
-                // TODO: trigger task to update tabs (doesnt need to be background)
                 bindTabs();
                 bindHeader();
             }