Merge "Use ActionBar in call log details activity."
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index fa0ffb2..4078598 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -16,9 +16,12 @@
 
 package com.android.contacts;
 
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.util.DataStatus;
 import com.android.contacts.util.StreamItemEntry;
 import com.android.contacts.util.StreamItemPhotoEntry;
+import com.google.android.collect.Lists;
 import com.google.common.annotations.VisibleForTesting;
 
 import android.content.ContentResolver;
@@ -44,23 +47,20 @@
 import android.provider.ContactsContract.DisplayNameSources;
 import android.provider.ContactsContract.Groups;
 import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.StreamItems;
 import android.provider.ContactsContract.StreamItemPhotos;
+import android.provider.ContactsContract.StreamItems;
 import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * Loads a single Contact and all it constituent RawContacts.
@@ -71,6 +71,7 @@
     private Uri mLookupUri;
     private boolean mLoadGroupMetaData;
     private boolean mLoadStreamItems;
+    private final boolean mLoadInvitableAccountTypes;
     private Result mContact;
     private ForceLoadContentObserver mObserver;
     private boolean mDestroyed;
@@ -113,6 +114,7 @@
         private final ArrayList<Entity> mEntities;
         private ArrayList<StreamItemEntry> mStreamItems;
         private final HashMap<Long, DataStatus> mStatuses;
+        private final ArrayList<String> mInvitableAccountTypes;
 
         private String mDirectoryDisplayName;
         private String mDirectoryType;
@@ -147,6 +149,7 @@
             mPhoneticName = null;
             mStarred = false;
             mPresence = null;
+            mInvitableAccountTypes = null;
         }
 
         /**
@@ -173,6 +176,7 @@
             mPhoneticName = phoneticName;
             mStarred = starred;
             mPresence = presence;
+            mInvitableAccountTypes = Lists.newArrayList();
         }
 
         private Result(Result from) {
@@ -193,6 +197,7 @@
             mEntities = from.mEntities;
             mStreamItems = from.mStreamItems;
             mStatuses = from.mStatuses;
+            mInvitableAccountTypes = from.mInvitableAccountTypes;
 
             mDirectoryDisplayName = from.mDirectoryDisplayName;
             mDirectoryType = from.mDirectoryType;
@@ -279,6 +284,10 @@
             return mPresence;
         }
 
+        public ArrayList<String> getInvitableAccontTypes() {
+            return mInvitableAccountTypes;
+        }
+
         public ArrayList<Entity> getEntities() {
             return mEntities;
         }
@@ -568,6 +577,9 @@
                         loadStreamItems(result);
                     }
                     loadPhotoBinaryData(result);
+                    if (mLoadInvitableAccountTypes) {
+                        loadInvitableAccountTypes(result);
+                    }
                 }
                 return result;
             } catch (Exception e) {
@@ -718,6 +730,27 @@
             }
         }
 
+        private void loadInvitableAccountTypes(Result contactData) {
+            Map<String, AccountType> allInvitables =
+                    AccountTypeManager.getInstance(getContext()).getInvitableAccountTypes();
+            if (allInvitables.isEmpty()) {
+                return;
+            }
+
+            HashMap<String, AccountType> result = new HashMap<String, AccountType>(allInvitables);
+
+            // Remove the ones that already has a raw contact in the current contact
+            for (Entity entity : contactData.getEntities()) {
+                final String type = entity.getEntityValues().getAsString(RawContacts.ACCOUNT_TYPE);
+                if (!TextUtils.isEmpty(type)) {
+                    result.remove(type);
+                }
+            }
+
+            // Set to mInvitableAccountTypes
+            contactData.mInvitableAccountTypes.addAll(result.keySet());
+        }
+
         /**
          * Extracts Contact level columns from the cursor.
          */
@@ -1058,15 +1091,16 @@
     }
 
     public ContactLoader(Context context, Uri lookupUri) {
-        this(context, lookupUri, false, false);
+        this(context, lookupUri, false, false, false);
     }
 
     public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
-            boolean loadStreamItems) {
+            boolean loadStreamItems, boolean loadInvitableAccountTypes) {
         super(context);
         mLookupUri = lookupUri;
         mLoadGroupMetaData = loadGroupMetaData;
         mLoadStreamItems = loadStreamItems;
+        mLoadInvitableAccountTypes = loadInvitableAccountTypes;
     }
 
     public Uri getLookupUri() {
diff --git a/src/com/android/contacts/ContactStatusUtil.java b/src/com/android/contacts/ContactStatusUtil.java
new file mode 100644
index 0000000..4aa24b0
--- /dev/null
+++ b/src/com/android/contacts/ContactStatusUtil.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.ContactsContract.StatusUpdates;
+
+/**
+ * Provides static function to get default contact status message.
+ */
+public class ContactStatusUtil {
+
+    private static final String TAG = "ContactStatusUtil";
+
+    public static String getStatusString(Context context, int presence) {
+        Resources resources = context.getResources();
+        switch (presence) {
+            case StatusUpdates.AVAILABLE:
+                return resources.getString(R.string.status_available);
+            case StatusUpdates.IDLE:
+            case StatusUpdates.AWAY:
+                return resources.getString(R.string.status_away);
+            case StatusUpdates.DO_NOT_DISTURB:
+                return resources.getString(R.string.status_busy);
+            case StatusUpdates.OFFLINE:
+            case StatusUpdates.INVISIBLE:
+            default:
+                return null;
+        }
+    }
+
+}
diff --git a/src/com/android/contacts/detail/ContactLoaderFragment.java b/src/com/android/contacts/detail/ContactLoaderFragment.java
index 034a8cc..daa6012 100644
--- a/src/com/android/contacts/detail/ContactLoaderFragment.java
+++ b/src/com/android/contacts/detail/ContactLoaderFragment.java
@@ -171,7 +171,7 @@
         public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
             Uri lookupUri = args.getParcelable(LOADER_ARG_CONTACT_URI);
             return new ContactLoader(mContext, lookupUri, true /* loadGroupMetaData */,
-                    true /* loadStreamItems */);
+                    true /* loadStreamItems */, false /* load invitable account types */);
         }
 
         @Override
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index e51fd5c..7322fc6 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -45,12 +45,13 @@
         Contacts.STARRED,                       // 4
         Contacts.CONTACT_PRESENCE,              // 5
         Contacts.CONTACT_CHAT_CAPABILITY,       // 6
-        Contacts.PHOTO_ID,                      // 7
-        Contacts.PHOTO_THUMBNAIL_URI,           // 8
-        Contacts.LOOKUP_KEY,                    // 9
-        Contacts.PHONETIC_NAME,                 // 10
-        Contacts.HAS_PHONE_NUMBER,              // 11
-        Contacts.IS_USER_PROFILE,               // 12
+        Contacts.CONTACT_STATUS,                // 7
+        Contacts.PHOTO_ID,                      // 8
+        Contacts.PHOTO_THUMBNAIL_URI,           // 9
+        Contacts.LOOKUP_KEY,                    // 10
+        Contacts.PHONETIC_NAME,                 // 11
+        Contacts.HAS_PHONE_NUMBER,              // 12
+        Contacts.IS_USER_PROFILE,               // 13
     };
 
     protected static final String[] PROJECTION_DATA = new String[] {
@@ -61,11 +62,12 @@
         Data.STARRED,                           // 4
         Data.CONTACT_PRESENCE,                  // 5
         Data.CONTACT_CHAT_CAPABILITY,           // 6
-        Data.PHOTO_ID,                          // 7
-        Data.PHOTO_THUMBNAIL_URI,               // 8
-        Data.LOOKUP_KEY,                        // 9
-        Data.PHONETIC_NAME,                     // 10
-        Data.HAS_PHONE_NUMBER,                  // 11
+        Data.CONTACT_STATUS,                    // 7
+        Data.PHOTO_ID,                          // 8
+        Data.PHOTO_THUMBNAIL_URI,               // 9
+        Data.LOOKUP_KEY,                        // 10
+        Data.PHONETIC_NAME,                     // 11
+        Data.HAS_PHONE_NUMBER,                  // 12
     };
 
     protected static final String[] FILTER_PROJECTION = new String[] {
@@ -76,13 +78,14 @@
         Contacts.STARRED,                       // 4
         Contacts.CONTACT_PRESENCE,              // 5
         Contacts.CONTACT_CHAT_CAPABILITY,       // 6
-        Contacts.PHOTO_ID,                      // 7
-        Contacts.PHOTO_THUMBNAIL_URI,           // 8
-        Contacts.LOOKUP_KEY,                    // 9
-        Contacts.PHONETIC_NAME,                 // 10
-        Contacts.HAS_PHONE_NUMBER,              // 11
-        Contacts.IS_USER_PROFILE,               // 12
-        SearchSnippetColumns.SNIPPET,           // 13
+        Contacts.CONTACT_STATUS,                // 7
+        Contacts.PHOTO_ID,                      // 8
+        Contacts.PHOTO_THUMBNAIL_URI,           // 9
+        Contacts.LOOKUP_KEY,                    // 10
+        Contacts.PHONETIC_NAME,                 // 11
+        Contacts.HAS_PHONE_NUMBER,              // 12
+        Contacts.IS_USER_PROFILE,               // 13
+        SearchSnippetColumns.SNIPPET,           // 14
     };
 
     protected static final int CONTACT_ID_COLUMN_INDEX = 0;
@@ -92,13 +95,14 @@
     protected static final int CONTACT_STARRED_COLUMN_INDEX = 4;
     protected static final int CONTACT_PRESENCE_STATUS_COLUMN_INDEX = 5;
     protected static final int CONTACT_CHAT_CAPABILITY_COLUMN_INDEX = 6;
-    protected static final int CONTACT_PHOTO_ID_COLUMN_INDEX = 7;
-    protected static final int CONTACT_PHOTO_URI_COLUMN_INDEX = 8;
-    protected static final int CONTACT_LOOKUP_KEY_COLUMN_INDEX = 9;
-    protected static final int CONTACT_PHONETIC_NAME_COLUMN_INDEX = 10;
-    protected static final int CONTACT_HAS_PHONE_COLUMN_INDEX = 11;
-    protected static final int CONTACT_IS_USER_PROFILE = 12;
-    protected static final int CONTACT_SNIPPET_COLUMN_INDEX = 13;
+    protected static final int CONTACT_CONTACT_STATUS_COLUMN_INDEX = 7;
+    protected static final int CONTACT_PHOTO_ID_COLUMN_INDEX = 8;
+    protected static final int CONTACT_PHOTO_URI_COLUMN_INDEX = 9;
+    protected static final int CONTACT_LOOKUP_KEY_COLUMN_INDEX = 10;
+    protected static final int CONTACT_PHONETIC_NAME_COLUMN_INDEX = 11;
+    protected static final int CONTACT_HAS_PHONE_COLUMN_INDEX = 12;
+    protected static final int CONTACT_IS_USER_PROFILE = 13;
+    protected static final int CONTACT_SNIPPET_COLUMN_INDEX = 14;
 
     private CharSequence mUnknownNameText;
     private int mDisplayNameColumnIndex;
@@ -304,9 +308,9 @@
         view.showPhoneticName(cursor, CONTACT_PHONETIC_NAME_COLUMN_INDEX);
     }
 
-    protected void bindPresence(final ContactListItemView view, Cursor cursor) {
-        view.showPresence(cursor, CONTACT_PRESENCE_STATUS_COLUMN_INDEX,
-                CONTACT_CHAT_CAPABILITY_COLUMN_INDEX);
+    protected void bindPresenceAndStatusMessage(final ContactListItemView view, Cursor cursor) {
+        view.showPresenceAndStatusMessage(cursor, CONTACT_PRESENCE_STATUS_COLUMN_INDEX,
+                CONTACT_CHAT_CAPABILITY_COLUMN_INDEX, CONTACT_CONTACT_STATUS_COLUMN_INDEX);
     }
 
     protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) {
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index 4fb6f1d..d69f880 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -17,6 +17,7 @@
 package com.android.contacts.list;
 
 import com.android.contacts.ContactPresenceIconUtil;
+import com.android.contacts.ContactStatusUtil;
 import com.android.contacts.R;
 import com.android.contacts.format.DisplayNameFormatter;
 import com.android.contacts.format.PrefixHighlighter;
@@ -916,7 +917,7 @@
             mStatusView.setSingleLine(true);
             mStatusView.setEllipsize(getTextEllipsis());
             mStatusView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
-            mStatusView.setText("Put Status here");   // Temporary
+            mStatusView.setTextColor(Color.GRAY);
             addView(mStatusView);
         }
         return mStatusView;
@@ -996,20 +997,33 @@
     }
 
     /**
-     * Sets the proper icon (star or presence or nothing)
+     * Sets the proper icon (star or presence or nothing) and/or status message.
      */
-    public void showPresence(Cursor cursor, int presenceColumnIndex, int capabilityColumnIndex) {
+    public void showPresenceAndStatusMessage(Cursor cursor, int presenceColumnIndex,
+            int capabilityColumnIndex, int contactStatusColumnIndex) {
         Drawable icon = null;
+        int presence = 0;
+        int chatCapability = 0;
         if (!cursor.isNull(presenceColumnIndex)) {
-            int status = cursor.getInt(presenceColumnIndex);
-            int chatCapability = 0;
+            presence = cursor.getInt(presenceColumnIndex);
             if (capabilityColumnIndex != 0 && !cursor.isNull(presenceColumnIndex)) {
                 chatCapability = cursor.getInt(capabilityColumnIndex);
             }
             icon = ContactPresenceIconUtil.getChatCapabilityIcon(
-                    getContext(), status, chatCapability);
+                    getContext(), presence, chatCapability);
         }
         setPresence(icon);
+
+        String statusMessage = null;
+        if (contactStatusColumnIndex != 0 && !cursor.isNull(contactStatusColumnIndex)) {
+            statusMessage = cursor.getString(contactStatusColumnIndex);
+        }
+        // If there is no status message from the contact, but there was a presence value, then use
+        // the default status message string
+        if (statusMessage == null && presence != 0) {
+            statusMessage = ContactStatusUtil.getStatusString(getContext(), presence);
+        }
+        setStatus(statusMessage);
     }
 
     /**
diff --git a/src/com/android/contacts/list/ContactTileView.java b/src/com/android/contacts/list/ContactTileView.java
index 715c331..6374c23 100644
--- a/src/com/android/contacts/list/ContactTileView.java
+++ b/src/com/android/contacts/list/ContactTileView.java
@@ -16,6 +16,7 @@
 package com.android.contacts.list;
 
 import com.android.contacts.ContactPhotoManager;
+import com.android.contacts.ContactStatusUtil;
 import com.android.contacts.R;
 import com.android.contacts.list.ContactTileAdapter.ContactEntry;
 
@@ -90,7 +91,8 @@
                     statusText = null;
                 } else {
                     statusText =
-                          (entry.status == null ? getStatusString(entry.presence) : entry.status);
+                          (entry.status != null ? entry.status :
+                          ContactStatusUtil.getStatusString(mContext, entry.presence));
                 }
                 mStatus.setText(statusText);
             }
@@ -119,23 +121,6 @@
         }
     }
 
-    private String getStatusString(int presence) {
-        Resources resources = getResources();
-        switch (presence) {
-            case StatusUpdates.AVAILABLE:
-                return resources.getString(R.string.status_available);
-            case StatusUpdates.IDLE:
-            case StatusUpdates.AWAY:
-                return resources.getString(R.string.status_away);
-            case StatusUpdates.DO_NOT_DISTURB:
-                return resources.getString(R.string.status_busy);
-            case StatusUpdates.OFFLINE:
-            case StatusUpdates.INVISIBLE:
-            default:
-                return null;
-        }
-    }
-
     public Uri getLookupUri() {
         return mLookupUri;
     }
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index c93b544..8b93888 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -234,7 +234,7 @@
         }
 
         bindName(view, cursor);
-        bindPresence(view, cursor);
+        bindPresenceAndStatusMessage(view, cursor);
 
         if (isSearchMode()) {
             bindSearchSnippet(view, cursor);
diff --git a/src/com/android/contacts/list/LegacyContactListAdapter.java b/src/com/android/contacts/list/LegacyContactListAdapter.java
index ffc8fc3..b3ab2af 100644
--- a/src/com/android/contacts/list/LegacyContactListAdapter.java
+++ b/src/com/android/contacts/list/LegacyContactListAdapter.java
@@ -91,6 +91,6 @@
     }
 
     protected void bindPresence(final ContactListItemView view, Cursor cursor) {
-        view.showPresence(cursor, PERSON_PRESENCE_STATUS_COLUMN_INDEX, 0);
+        view.showPresenceAndStatusMessage(cursor, PERSON_PRESENCE_STATUS_COLUMN_INDEX, 0, 0);
     }
 }
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index 70aa430..5de4340 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -33,6 +33,7 @@
 import android.provider.ContactsContract.RawContacts;
 import android.widget.EditText;
 
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -120,7 +121,7 @@
      */
     public CharSequence getInviteContactActionLabel(Context context) {
         return getResourceText(context, summaryResPackageName, getInviteContactActionResId(context),
-                null);
+                "");
     }
 
     /**
@@ -342,4 +343,28 @@
         public CharSequence inflateUsing(Context context, Cursor cursor);
         public CharSequence inflateUsing(Context context, ContentValues values);
     }
+
+    /**
+     * Compare two {@link AccountType} by their {@link AccountType#getDisplayLabel} with the
+     * current locale.
+     */
+    public static class DisplayLabelComparator implements Comparator<AccountType> {
+        private final Context mContext;
+        /** {@link Comparator} for the current locale. */
+        private final Collator mCollator = Collator.getInstance();
+
+        public DisplayLabelComparator(Context context) {
+            mContext = context;
+        }
+
+        private String getDisplayLabel(AccountType type) {
+            CharSequence label = type.getDisplayLabel(mContext);
+            return (label == null) ? "" : label.toString();
+        }
+
+        @Override
+        public int compare(AccountType lhs, AccountType rhs) {
+            return mCollator.compare(getDisplayLabel(lhs), getDisplayLabel(rhs));
+        }
+    }
 }
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
index d59aebd..3d7881b 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
@@ -111,7 +111,8 @@
             // Not yet set-up (this can happen while the Configuration activity is visible)
             return;
         }
-        final ContactLoader contactLoader = new ContactLoader(context, contactUri, false, true);
+        final ContactLoader contactLoader = new ContactLoader(context, contactUri, false, true,
+                false);
         contactLoader.registerListener(0,
                 new ContactLoader.OnLoadCompleteListener<ContactLoader.Result>() {
                     @Override
diff --git a/tests/src/com/android/contacts/model/AccountTypeTest.java b/tests/src/com/android/contacts/model/AccountTypeTest.java
index 4898cf3..de66694 100644
--- a/tests/src/com/android/contacts/model/AccountTypeTest.java
+++ b/tests/src/com/android/contacts/model/AccountTypeTest.java
@@ -17,9 +17,14 @@
 package com.android.contacts.model;
 
 import com.android.contacts.tests.R;
+import com.google.common.collect.Lists;
 
 import android.content.Context;
 import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+import java.util.ArrayList;
+import java.util.Collections;
 
 /**
  * Test case for {@link AccountType}.
@@ -87,4 +92,53 @@
         assertEquals(getTestContext().getString(externalResID),
                 accountType.getInviteContactActionLabel(c));
     }
+
+    public void testDisplayLabelComparator() {
+        final AccountTypeForDisplayLabelTest EMPTY = new AccountTypeForDisplayLabelTest("");
+        final AccountTypeForDisplayLabelTest NULL = new AccountTypeForDisplayLabelTest(null);
+        final AccountTypeForDisplayLabelTest AA = new AccountTypeForDisplayLabelTest("aa");
+        final AccountTypeForDisplayLabelTest BBB = new AccountTypeForDisplayLabelTest("bbb");
+        final AccountTypeForDisplayLabelTest C = new AccountTypeForDisplayLabelTest("c");
+
+        assertTrue(compareDisplayLabel(AA, BBB) < 0);
+        assertTrue(compareDisplayLabel(BBB, C) < 0);
+        assertTrue(compareDisplayLabel(AA, C) < 0);
+        assertTrue(compareDisplayLabel(AA, AA) == 0);
+        assertTrue(compareDisplayLabel(BBB, AA) > 0);
+
+        assertTrue(compareDisplayLabel(EMPTY, AA) < 0);
+        assertTrue(compareDisplayLabel(EMPTY, NULL) == 0);
+    }
+
+    private int compareDisplayLabel(AccountType lhs, AccountType rhs) {
+        return new AccountType.DisplayLabelComparator(getContext()).compare(lhs, rhs);
+    }
+
+    private class AccountTypeForDisplayLabelTest extends AccountType {
+        private final String mDisplayLabel;
+
+        public AccountTypeForDisplayLabelTest(String displayLabel) {
+            mDisplayLabel = displayLabel;
+        }
+
+        @Override
+        public CharSequence getDisplayLabel(Context context) {
+            return mDisplayLabel;
+        }
+
+        @Override
+        public int getHeaderColor(Context context) {
+            return 0;
+        }
+
+        @Override
+        public int getSideBarColor(Context context) {
+            return 0;
+        }
+
+        @Override
+        public boolean isGroupMembershipEditable() {
+            return false;
+        }
+    }
 }