Merge change 26772 into eclair

* changes:
  Fixes the issue about fasttrack window not being dismissed on tapping just outside it.
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 624885c..2c66748 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -345,7 +345,10 @@
     <string name="starredInAndroid">Starred in Android</string>
 
     <!-- Displayed in a spinner dialog after the user creates a contact and it's being saved to the database -->
-    <string name="savingContact">Saving contact...</string>
+    <string name="savingContact">Saving contact\u2026</string>
+
+    <!-- Displayed in a spinner dialog as user changes to display groups are saved -->
+    <string name="savingDisplayGroups">Saving display groups\u2026</string>
 
     <!-- Toast displayed when a contact is created -->
     <string name="contactCreatedToast">Contact created.</string>
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 79af879..bae9b5c 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -926,8 +926,8 @@
                     sources.getInflatedSource(account.type,
                             ContactsSource.LEVEL_SUMMARY);
 
-                text1.setText(source.getDisplayLabel(ContactsListActivity.this));
-                text2.setText(account.name);
+                text1.setText(account.name);
+                text2.setText(source.getDisplayLabel(ContactsListActivity.this));
 
                 return convertView;
             }
@@ -1719,7 +1719,7 @@
             case MODE_JOIN_CONTACT:
                 mQueryHandler.setLoadingJoinSuggestions(true);
                 mQueryHandler.startQuery(QUERY_TOKEN, null, getJoinSuggestionsUri(null), projection,
-                        null, null, null);
+                        Contacts._ID + " != " + mQueryAggregateId, null, null);
                 break;
         }
     }
@@ -1795,7 +1795,8 @@
                         null, null);
                 mAdapter.setSuggestionsCursor(cursor);
                 return resolver.query(getContactFilterUri(filter), projection,
-                        getContactSelection(), null, getSortOrder(projection));
+                        Contacts._ID + " != " + mQueryAggregateId, null,
+                        getSortOrder(projection));
             }
         }
         throw new UnsupportedOperationException("filtering not allowed in mode " + mMode);
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index 70c1867..9b22b4b 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -862,6 +862,11 @@
                 final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
                 final long rawContactId = entValues.getAsLong(RawContacts._ID);
 
+                if (!mRawContactIds.contains(rawContactId)) {
+                    mRawContactIds.add(rawContactId);
+                }
+
+
 //                // This performs the tab filtering
 //                if (mSelectedRawContactId != null
 //                        && mSelectedRawContactId != rawContactId
@@ -903,10 +908,6 @@
                         continue;
                     }
 
-                    if (!mRawContactIds.contains(entry.contactId)) {
-                        mRawContactIds.add(entry.contactId);
-                    }
-
                     if (CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mimetype)
                             || CommonDataKinds.Email.CONTENT_ITEM_TYPE.equals(mimetype)
                             || CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE.equals(mimetype)
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index 0d8ede9..1d78cfc 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -33,9 +33,11 @@
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.Intents.Insert;
+import android.provider.ContactsContract;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -378,8 +380,12 @@
                     continue;
                 }
 
-                // Test and remove this row if empty
-                if (EntityModifier.isEmpty(entry, kind)) {
+                // Test and remove this row if empty and it isn't a photo from google
+                final boolean isGoogleSource = TextUtils.equals(GoogleSource.ACCOUNT_TYPE,
+                        entry.getAsString(RawContacts.ACCOUNT_TYPE));
+                final boolean isPhoto = TextUtils.equals(Photo.CONTENT_ITEM_TYPE, kind.mimeType);
+                final boolean isGooglePhoto = isPhoto && isGoogleSource;
+                if (EntityModifier.isEmpty(entry, kind) && !isGooglePhoto) {
                     // TODO: remove this verbose logging
                     Log.w(TAG, "Trimming: " + entry.toString());
                     entry.markDeleted();
diff --git a/src/com/android/contacts/model/EntitySet.java b/src/com/android/contacts/model/EntitySet.java
index 02a127e..2137987 100644
--- a/src/com/android/contacts/model/EntitySet.java
+++ b/src/com/android/contacts/model/EntitySet.java
@@ -216,7 +216,7 @@
         final int size = this.size();
         for (int i = 0; i < size; i++) {
             final Long currentId = getRawContactId(i);
-            if (currentId == rawContactId) {
+            if (rawContactId.equals(currentId)) {
                 return i;
             }
         }
diff --git a/src/com/android/contacts/model/ExchangeSource.java b/src/com/android/contacts/model/ExchangeSource.java
index 5c2d024..0a7fb23 100644
--- a/src/com/android/contacts/model/ExchangeSource.java
+++ b/src/com/android/contacts/model/ExchangeSource.java
@@ -123,6 +123,8 @@
             kind.typeList.add(buildPhoneType(Phone.TYPE_MMS).setSecondary(true).setSpecificMax(1));
             kind.typeList
                     .add(buildPhoneType(Phone.TYPE_RADIO).setSecondary(true).setSpecificMax(1));
+            kind.typeList.add(buildPhoneType(Phone.TYPE_ASSISTANT).setSecondary(true)
+                    .setSpecificMax(1).setCustomColumn(Phone.LABEL));
             kind.typeList.add(buildPhoneType(Phone.TYPE_CUSTOM).setSecondary(true)
                     .setSpecificMax(1).setCustomColumn(Phone.LABEL));
 
diff --git a/src/com/android/contacts/ui/DisplayGroupsActivity.java b/src/com/android/contacts/ui/DisplayGroupsActivity.java
index ffe423f..ab6f651 100644
--- a/src/com/android/contacts/ui/DisplayGroupsActivity.java
+++ b/src/com/android/contacts/ui/DisplayGroupsActivity.java
@@ -16,15 +16,24 @@
 
 package com.android.contacts.ui;
 
+import com.android.contacts.R;
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.Sources;
+import com.android.contacts.util.EmptyService;
+import com.android.contacts.util.WeakAsyncTask;
+import com.google.android.collect.Sets;
+
 import android.accounts.Account;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.ExpandableListActivity;
+import android.app.ProgressDialog;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
 import android.content.pm.PackageManager;
@@ -36,6 +45,7 @@
 import android.os.Bundle;
 import android.preference.PreferenceManager;
 import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Groups;
 import android.provider.ContactsContract.Settings;
 import android.view.ContextMenu;
@@ -52,13 +62,7 @@
 import android.widget.TextView;
 import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
 
-import com.google.android.collect.Sets;
-
-import com.android.contacts.R;
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.Sources;
-import com.android.contacts.util.WeakAsyncTask;
-
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashSet;
 
@@ -91,6 +95,12 @@
     private View mHeaderPhones;
     private View mHeaderSeparator;
 
+    private static final Uri sDelayedSettings = Settings.CONTENT_URI.buildUpon()
+            .appendQueryParameter(Contacts.DELAY_STARRED_UPDATE, "1").build();
+
+    private static final Uri sDelayedGroups = Groups.CONTENT_URI.buildUpon()
+            .appendQueryParameter(Contacts.DELAY_STARRED_UPDATE, "1").build();
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -253,13 +263,12 @@
         final ContentResolver resolver = getContentResolver();
         final ContentValues values = new ContentValues();
 
-        // TODO: heavy update, perhaps push to background query
         if (id == UNGROUPED_ID) {
             // Handle persisting for ungrouped through Settings
             values.put(Settings.UNGROUPED_VISIBLE, checkbox.isChecked() ? 1 : 0);
 
             final Cursor settings = mAdapter.getGroup(groupPosition);
-            final int count = resolver.update(Settings.CONTENT_URI, values, Groups.ACCOUNT_NAME
+            final int count = resolver.update(sDelayedSettings, values, Groups.ACCOUNT_NAME
                     + "=? AND " + Groups.ACCOUNT_TYPE + "=?", new String[] {
                     settings.getString(SettingsQuery.ACCOUNT_NAME),
                     settings.getString(SettingsQuery.ACCOUNT_TYPE)
@@ -271,7 +280,7 @@
             // Handle persisting for normal group
             values.put(Groups.GROUP_VISIBLE, checkbox.isChecked() ? 1 : 0);
 
-            final Uri groupUri = ContentUris.withAppendedId(Groups.CONTENT_URI, id);
+            final Uri groupUri = ContentUris.withAppendedId(sDelayedGroups, id);
             final int count = resolver.update(groupUri, values, null, null);
         }
 
@@ -412,7 +421,7 @@
         if (groupId == UNGROUPED_ID) {
             // Updating the overall syncing flag for this account
             values.put(Settings.SHOULD_SYNC, shouldSync ? 1 : 0);
-            resolver.update(Settings.CONTENT_URI, values, Settings.ACCOUNT_NAME + "=? AND "
+            resolver.update(sDelayedSettings, values, Settings.ACCOUNT_NAME + "=? AND "
                     + Settings.ACCOUNT_TYPE + "=?", new String[] {
                     account.name, account.type
             });
@@ -421,7 +430,7 @@
                 // If syncing mode is everything, force-enable all children groups
                 values.clear();
                 values.put(Groups.SHOULD_SYNC, shouldSync ? 1 : 0);
-                resolver.update(Groups.CONTENT_URI, values, Groups.ACCOUNT_NAME + "=? AND "
+                resolver.update(sDelayedGroups, values, Groups.ACCOUNT_NAME + "=? AND "
                         + Groups.ACCOUNT_TYPE + "=?", new String[] {
                         account.name, account.type
                 });
@@ -429,13 +438,13 @@
         } else {
             // Treat as normal group
             values.put(Groups.SHOULD_SYNC, shouldSync ? 1 : 0);
-            resolver.update(Groups.CONTENT_URI, values, Groups._ID + "=" + groupId, null);
+            resolver.update(sDelayedGroups, values, Groups._ID + "=" + groupId, null);
 
             if (syncMode == SYNC_MODE_EVERYTHING && !shouldSync) {
                 // Remove "everything" from sync, user has already been warned
                 values.clear();
                 values.put(Settings.SHOULD_SYNC, shouldSync ? 1 : 0);
-                resolver.update(Settings.CONTENT_URI, values, Settings.ACCOUNT_NAME + "=? AND "
+                resolver.update(sDelayedSettings, values, Settings.ACCOUNT_NAME + "=? AND "
                         + Settings.ACCOUNT_TYPE + "=?", new String[] {
                         account.name, account.type
                 });
@@ -443,6 +452,71 @@
         }
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public void onBackPressed() {
+        // TODO: somehow update visibility when user leaves through a different
+        // path, never actually pressing the back key
+        new UpdateTask(this).execute();
+    }
+
+    /**
+     * Background task that uses {@link Contacts#FORCE_STARRED_UPDATE} to force
+     * update of {@link Contacts#IN_VISIBLE_GROUP}, showing spinner dialog to
+     * user while updating.
+     */
+    public static class UpdateTask extends WeakAsyncTask<Void, Void, Void, Activity> {
+        private WeakReference<ProgressDialog> mProgress;
+
+        public UpdateTask(Activity target) {
+            super(target);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        protected void onPreExecute(Activity target) {
+            final Context context = target;
+
+            mProgress = new WeakReference<ProgressDialog>(ProgressDialog.show(target, null,
+                    target.getText(R.string.savingDisplayGroups)));
+
+            // Before starting this task, start an empty service to protect our
+            // process from being reclaimed by the system.
+            context.startService(new Intent(context, EmptyService.class));
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        protected Void doInBackground(Activity target, Void... params) {
+            final Context context = target;
+
+            final ContentValues values = new ContentValues();
+            final ContentResolver resolver = context.getContentResolver();
+
+            // Push through an empty update to trigger forced refresh
+            final Uri forcedGroups = Groups.CONTENT_URI.buildUpon().appendQueryParameter(
+                    Contacts.FORCE_STARRED_UPDATE, "1").build();
+            resolver.update(forcedGroups, values, null, null);
+
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        protected void onPostExecute(Activity target, Void result) {
+            final Context context = target;
+
+            final ProgressDialog dialog = mProgress.get();
+            if (dialog != null) dialog.dismiss();
+
+            target.finish();
+
+            // Stop the service that was protecting us
+            context.stopService(new Intent(context, EmptyService.class));
+        }
+    }
+
+
     /**
      * Return the best title for the {@link Groups} entry at the current
      * {@link Cursor} position.
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index c3a3935..9b380df 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -765,8 +765,8 @@
                     final ContactsSource source = sources.getInflatedSource(account.type,
                             ContactsSource.LEVEL_SUMMARY);
 
-                    text1.setText(source.getDisplayLabel(target));
-                    text2.setText(account.name);
+                    text1.setText(account.name);
+                    text2.setText(source.getDisplayLabel(target));
 
                     return convertView;
                 }
diff --git a/src/com/android/contacts/ui/widget/GenericEditorView.java b/src/com/android/contacts/ui/widget/GenericEditorView.java
index 4490fbf..53dd2e0 100644
--- a/src/com/android/contacts/ui/widget/GenericEditorView.java
+++ b/src/com/android/contacts/ui/widget/GenericEditorView.java
@@ -74,6 +74,8 @@
     protected boolean mHideOptional = true;
 
     protected EditType mType;
+    // Used only when a user tries to use custom label.
+    private EditType mPendingType;
 
     public GenericEditorView(Context context) {
         super(context);
@@ -221,7 +223,7 @@
      * If the final value is empty, this change request is ignored;
      * no empty text is allowed in any custom label.
      */
-    public Dialog createCustomDialog() {
+    private Dialog createCustomDialog() {
         final EditText customType = new EditText(mContext);
         customType.setInputType(INPUT_TYPE_CUSTOM);
         customType.requestFocus();
@@ -234,6 +236,10 @@
             public void onClick(DialogInterface dialog, int which) {
                 final String customText = customType.getText().toString().trim();
                 if (!TextUtils.isEmpty(customText)) {
+                    // Now we're sure it's ok to actually change the type value.
+                    mType = mPendingType;
+                    mPendingType = null;
+                    mEntry.put(mKind.typeColumn, mType.rawValue);
                     mEntry.put(mType.customColumn, customText);
                     rebuildLabel();
                 }
@@ -274,18 +280,22 @@
             }
         };
 
-        final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
+        final DialogInterface.OnClickListener clickListener =
+                new DialogInterface.OnClickListener() {
             public void onClick(DialogInterface dialog, int which) {
                 dialog.dismiss();
 
-                // User picked type, so write to entry
-                mType = validTypes.get(which);
-                mEntry.put(mKind.typeColumn, mType.rawValue);
-
                 if (mType.customColumn != null) {
-                    // Show custom label dialog if requested by type
+                    // Show custom label dialog if requested by type.
+                    //
+                    // Only when the custum value input in the next step is correct one.
+                    // this method also set the type value to what the user requested here.
+                    mPendingType = validTypes.get(which);
                     createCustomDialog().show();
                 } else {
+                    // User picked type, and we're sure it's ok to actually write the entry.
+                    mType = validTypes.get(which);
+                    mEntry.put(mKind.typeColumn, mType.rawValue);
                     rebuildLabel();
                 }
             }