Merge change 26770 into eclair

* changes:
  Show a line at the bottom of favorites.
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 624885c..f4e3465 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -21,7 +21,7 @@
 
     <!-- Title for the activity that dials the phone.  This is the name
          used in the Launcher icon. -->
-    <string name="launcherDialer">Dialer</string>
+    <string name="launcherDialer">Phone</string>
 
     <!-- Name of activity that allows users to create shortcuts on the home screen to a contact.
          This shows up in a list of things like bookmark, folder, music playlist, etc -->
@@ -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>
@@ -408,7 +411,7 @@
     <string name="contactsFavoritesLabel">Favorites</string>
 
     <!-- The description text for the dialer tab. Space is limited for this string, so the shorter the better -->
-    <string name="dialerIconLabel">Dialer</string>
+    <string name="dialerIconLabel">Phone</string>
 
     <!-- The description text for the call log tab. Space is limited for this string, so the shorter the better -->
     <string name="recentCallsIconLabel">Call log</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/DialtactsActivity.java b/src/com/android/contacts/DialtactsActivity.java
index 9fb5f41..208fbf4 100644
--- a/src/com/android/contacts/DialtactsActivity.java
+++ b/src/com/android/contacts/DialtactsActivity.java
@@ -33,9 +33,11 @@
 import android.widget.TabHost;
 
 /**
- * The dialer activity that has one tab with the virtual 12key dialer,
- * and another tab with recent calls in it. This is the container and the tabs
- * are embedded using intents.
+ * The dialer activity that has one tab with the virtual 12key
+ * dialer, a tab with recent calls in it, a tab with the contacts and
+ * a tab with the favorite. This is the container and the tabs are
+ * embedded using intents.
+ * The dialer tab's title is 'phone', a more common name (see strings.xml).
  */
 public class DialtactsActivity extends TabActivity implements TabHost.OnTabChangeListener {
     private static final String TAG = "Dailtacts";
@@ -46,7 +48,7 @@
     private static final int TAB_INDEX_CALL_LOG = 1;
     private static final int TAB_INDEX_CONTACTS = 2;
     private static final int TAB_INDEX_FAVORITES = 3;
-    
+
     static final String EXTRA_IGNORE_STATE = "ignore-state";
 
     /** Name of the dialtacts shared preferences */
@@ -56,7 +58,7 @@
     static final boolean PREF_FAVORITES_AS_CONTACTS_DEFAULT = false;
 
     private TabHost mTabHost;
-    private String mFilterText;    
+    private String mFilterText;
     private Uri mDialUri;
 
     @Override
@@ -65,7 +67,7 @@
 
         final Intent intent = getIntent();
         fixIntent(intent);
-        
+
         requestWindowFeature(Window.FEATURE_NO_TITLE);
         setContentView(R.layout.dialer_activity);
 
@@ -89,7 +91,7 @@
     @Override
     protected void onPause() {
         super.onPause();
-        
+
         int currentTabIndex = mTabHost.getCurrentTab();
         if (currentTabIndex == TAB_INDEX_CONTACTS || currentTabIndex == TAB_INDEX_FAVORITES) {
             SharedPreferences.Editor editor = getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE)
@@ -98,7 +100,7 @@
             editor.commit();
         }
     }
-    
+
     private void fixIntent(Intent intent) {
         // This should be cleaned up: the call key used to send an Intent
         // that just said to go to the recent calls list.  It now sends this
@@ -109,7 +111,7 @@
             setIntent(intent);
         }
     }
-    
+
     private void setupCallLogTab() {
         // Force the class since overriding tab entries doesn't work
         Intent intent = new Intent("com.android.phone.action.RECENT_CALLS");
@@ -153,10 +155,10 @@
 
     /**
      * Returns true if the intent is due to hitting the green send key while in a call.
-     * 
+     *
      * @param intent the intent that launched this activity
      * @param recentCallsRequest true if the intent is requesting to view recent calls
-     * @return true if the intent is due to hitting the green send key while in a call 
+     * @return true if the intent is due to hitting the green send key while in a call
      */
     private boolean isSendKeyWhileInCall(final Intent intent, final boolean recentCallsRequest) {
         // If there is a call in progress go to the call screen
@@ -178,7 +180,7 @@
 
     /**
      * Sets the current tab based on the intent's request type
-     * 
+     *
      * @param recentCallsRequest true is the recent calls tab is desired, false otherwise
      */
     private void setCurrentTab(Intent intent) {
@@ -188,7 +190,7 @@
             finish();
             return;
         }
-        
+
         // Dismiss menu provided by any children activities
         Activity activity = getLocalActivityManager().
                 getActivity(mTabHost.getCurrentTabTag());
@@ -253,13 +255,13 @@
         }
         return false;
     }
-    
+
     /**
      * Retrieves the filter text stored in {@link #setupFilterText(Intent)}.
      * This text originally came from a FILTER_CONTACTS_ACTION intent received
      * by this activity. The stored text will then be cleared after after this
      * method returns.
-     * 
+     *
      * @return The stored filter text
      */
     public String getAndClearFilterText() {
@@ -271,7 +273,7 @@
     /**
      * Stores the filter text associated with a FILTER_CONTACTS_ACTION intent.
      * This is so child activities can check if they are supposed to display a filter.
-     * 
+     *
      * @param intent The intent received in {@link #onNewIntent(Intent)}
      */
     private void setupFilterText(Intent intent) {
@@ -289,7 +291,7 @@
      * Retrieves the uri stored in {@link #setupDialUri(Intent)}. This uri
      * originally came from a dial intent received by this activity. The stored
      * uri will then be cleared after after this method returns.
-     * 
+     *
      * @return The stored uri
      */
     public Uri getAndClearDialUri() {
@@ -301,7 +303,7 @@
     /**
      * Stores the uri associated with a dial intent. This is so child activities can
      * check if they are supposed to display new dial info.
-     * 
+     *
      * @param intent The intent received in {@link #onNewIntent(Intent)}
      */
     private void setupDialUri(Intent intent) {
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/FastTrackWindow.java b/src/com/android/contacts/ui/FastTrackWindow.java
index 9d5740a..769b957 100644
--- a/src/com/android/contacts/ui/FastTrackWindow.java
+++ b/src/com/android/contacts/ui/FastTrackWindow.java
@@ -1175,6 +1175,8 @@
         if (event.getAction() == MotionEvent.ACTION_DOWN) {
             // Only try detecting outside events on down-press
             mDecor.getHitRect(mRect);
+	    mRect.top = mRect.top + mDecor.getPaddingTop();
+	    mRect.bottom = mRect.bottom - mDecor.getPaddingBottom();
             final int x = (int)event.getX();
             final int y = (int)event.getY();
             if (!mRect.contains(x, y)) {
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();
                 }
             }