Support for local profile

Bug: 5121834 Support local profile
     5086184 Account name is overlapped by number of contacts
     5082317 Text is chopped on the top in contact list

1. New headers were added at the top of the contact list to
   present an empty local profile.
2. Clicking the empty local profile opens the editor to allow
   the user to create a local profile.
3. Profiles are shown at the top of the contacts list with the
   "ME" header and the number of contatcs.
4. "Add to contacts" button and the starred button were removed
   from the details view when it is a profile.
5. Fixed a problem with a header view that remained when you had
   a profile or was in search mode.
6. Fixed problem with contacts count apearing in search mode

Change-Id: I45615616e03a603759888d9e7169a853b3328b14
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index 9daa1e0..8416721 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -128,6 +128,7 @@
         private byte[] mPhotoBinaryData;
         private boolean mSendToVoicemail;
         private String mCustomRingtone;
+        private boolean mIsUserProfile;
 
         /**
          * Constructor for case "no contact found". This must only be used for the
@@ -154,6 +155,8 @@
             mInvitableAccountTypes = null;
             mSendToVoicemail = false;
             mCustomRingtone = null;
+            mIsUserProfile = false;
+
         }
 
         /**
@@ -162,7 +165,8 @@
         private Result(Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id,
                 long nameRawContactId, int displayNameSource, long photoId, String photoUri,
                 String displayName, String altDisplayName, String phoneticName, boolean starred,
-                Integer presence, boolean sendToVoicemail, String customRingtone) {
+                Integer presence, boolean sendToVoicemail, String customRingtone,
+                boolean isUserProfile) {
             mLookupUri = lookupUri;
             mUri = uri;
             mDirectoryId = directoryId;
@@ -183,6 +187,7 @@
             mInvitableAccountTypes = Lists.newArrayList();
             mSendToVoicemail = sendToVoicemail;
             mCustomRingtone = customRingtone;
+            mIsUserProfile = isUserProfile;
         }
 
         private Result(Result from) {
@@ -217,6 +222,7 @@
             mPhotoBinaryData = from.mPhotoBinaryData;
             mSendToVoicemail = from.mSendToVoicemail;
             mCustomRingtone = from.mCustomRingtone;
+            mIsUserProfile = from.mIsUserProfile;
         }
 
         /**
@@ -394,6 +400,10 @@
         public String getCustomRingtone() {
             return mCustomRingtone;
         }
+
+        public boolean isUserProfile() {
+            return mIsUserProfile;
+        }
     }
 
     /**
@@ -471,6 +481,7 @@
                 Contacts.PHOTO_URI,
                 Contacts.SEND_TO_VOICEMAIL,
                 Contacts.CUSTOM_RINGTONE,
+                Contacts.IS_USER_PROFILE,
         };
 
         public final static int NAME_RAW_CONTACT_ID = 0;
@@ -542,6 +553,7 @@
         public final static int PHOTO_URI = 61;
         public final static int SEND_TO_VOICEMAIL = 62;
         public final static int CUSTOM_RINGTONE = 63;
+        public final static int IS_USER_PROFILE = 64;
     }
 
     /**
@@ -808,6 +820,7 @@
                     : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
             final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
             final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
+            final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
 
             Uri lookupUri;
             if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
@@ -820,7 +833,7 @@
             return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
                     nameRawContactId, displayNameSource, photoId, photoUri, displayName,
                     altDisplayName, phoneticName, starred, presence, sendToVoicemail,
-                    customRingtone);
+                    customRingtone, isUserProfile);
         }
 
         /**
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 221796a..3bb330f 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -46,6 +46,7 @@
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Groups;
+import android.provider.ContactsContract.Profile;
 import android.provider.ContactsContract.RawContacts;
 import android.util.Log;
 import android.widget.Toast;
@@ -75,6 +76,7 @@
     public static final String ACTION_SAVE_CONTACT = "saveContact";
     public static final String EXTRA_CONTACT_STATE = "state";
     public static final String EXTRA_SAVE_MODE = "saveMode";
+    public static final String EXTRA_SAVE_IS_PROFILE = "saveIsProfile";
 
     public static final String ACTION_CREATE_GROUP = "createGroup";
     public static final String ACTION_RENAME_GROUP = "renameGroup";
@@ -269,12 +271,13 @@
      * using data presented as a set of ContentValues.
      */
     public static Intent createSaveContactIntent(Context context, EntityDeltaList state,
-            String saveModeExtraKey, int saveMode, Class<?> callbackActivity,
+            String saveModeExtraKey, int saveMode, boolean isProfile, Class<?> callbackActivity,
             String callbackAction) {
         Intent serviceIntent = new Intent(
                 context, ContactSaveService.class);
         serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT);
         serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state);
+        serviceIntent.putExtra(EXTRA_SAVE_IS_PROFILE, isProfile);
 
         // Callback intent will be invoked by the service once the contact is
         // saved.  The service will put the URI of the new contact as "data" on
@@ -289,6 +292,7 @@
     private void saveContact(Intent intent) {
         EntityDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);
         Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
+        boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false);
 
         // Trim any empty fields, and RawContacts, before persisting
         final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
@@ -321,7 +325,8 @@
                     throw new IllegalStateException("Could not determine RawContact ID after save");
                 }
                 final Uri rawContactUri = ContentUris.withAppendedId(
-                        RawContacts.CONTENT_URI, rawContactId);
+                        isProfile ? Profile.CONTENT_RAW_CONTACTS_URI : RawContacts.CONTENT_URI,
+                                rawContactId);
                 lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri);
                 Log.v(TAG, "Saved contact. New URI: " + lookupUri);
                 break;
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index 0c59695..3bff950 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -209,7 +209,7 @@
      */
     public static void setStarred(Result contactData, CheckBox starredView) {
         // Check if the starred state should be visible
-        if (!contactData.isDirectoryEntry()) {
+        if (!contactData.isDirectoryEntry() && !contactData.isUserProfile()) {
             starredView.setVisibility(View.VISIBLE);
             starredView.setChecked(contactData.getStarred());
         } else {
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 02e74dd..383f23b 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -1851,6 +1851,9 @@
             // Only local contacts
             if (mContactData == null || mContactData.isDirectoryEntry()) return false;
 
+            // User profile cannot be added to contacts
+            if (mContactData.isUserProfile()) return false;
+
             // Only if exactly one raw contact
             if (mContactData.getEntities().size() != 1) return false;
 
@@ -1923,7 +1926,7 @@
             // and fire off the intent. we don't need a callback, as the database listener
             // should update the ui
             final Intent intent = ContactSaveService.createSaveContactIntent(getActivity(),
-                    contactDeltaList, "", 0, getActivity().getClass(),
+                    contactDeltaList, "", 0, false, getActivity().getClass(),
                     UI.LIST_ALL_CONTACTS_ACTION);
             getActivity().startService(intent);
         }
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 0227b13..4126425 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -116,6 +116,8 @@
     private static final String KEY_SHOW_JOIN_SUGGESTIONS = "showJoinSuggestions";
     private static final String KEY_ENABLED = "enabled";
     private static final String KEY_STATUS = "status";
+    private static final String KEY_NEW_LOCAL_PROFILE = "newLocalProfile";
+    private static final String KEY_IS_USER_PROFILE = "isUserProfile";
 
     public static final String SAVE_MODE_EXTRA_KEY = "saveMode";
 
@@ -125,6 +127,8 @@
      */
     public static final String INTENT_EXTRA_ADD_TO_DEFAULT_DIRECTORY = "addToDefaultDirectory";
 
+    public static final String INTENT_EXTRA_NEW_LOCAL_PROFILE = "newLocalProfile";
+
     /**
      * Modes that specify what the AsyncTask has to perform after saving
      */
@@ -245,6 +249,8 @@
 
     private boolean mEnabled = true;
     private boolean mRequestFocus;
+    private boolean mNewLocalProfile = false;
+    private boolean mIsUserProfile = false;
 
     public ContactEditorFragment() {
     }
@@ -348,6 +354,8 @@
         mIntentExtras = intentExtras;
         mAutoAddToDefaultGroup = mIntentExtras != null
                 && mIntentExtras.containsKey(INTENT_EXTRA_ADD_TO_DEFAULT_DIRECTORY);
+        mNewLocalProfile = mIntentExtras != null
+            && mIntentExtras.getBoolean(INTENT_EXTRA_NEW_LOCAL_PROFILE);
     }
 
     public void setListener(Listener value) {
@@ -383,6 +391,8 @@
             mAggregationSuggestionsRawContactId = savedState.getLong(KEY_SHOW_JOIN_SUGGESTIONS);
             mEnabled = savedState.getBoolean(KEY_ENABLED);
             mStatus = savedState.getInt(KEY_STATUS);
+            mNewLocalProfile = savedState.getBoolean(KEY_NEW_LOCAL_PROFILE);
+            mIsUserProfile = savedState.getBoolean(KEY_IS_USER_PROFILE);
         }
     }
 
@@ -430,6 +440,13 @@
         setIntentExtras(mIntentExtras);
         mIntentExtras = null;
 
+        // For user profile, change the contacts query URI
+        mIsUserProfile = data.isUserProfile();
+        if (mIsUserProfile) {
+            for (EntityDelta state : mState) {
+                state.setProfileQueryUri();
+            }
+        }
         mRequestFocus = true;
 
         bindEditors();
@@ -463,8 +480,8 @@
     private void createContact() {
         final List<AccountWithDataSet> accounts =
                 AccountTypeManager.getInstance(mContext).getAccounts(true);
-        // No Accounts available.  Create a phone-local contact.
-        if (accounts.isEmpty()) {
+        // No Accounts available or creating a local profile.  Create a phone-local contact.
+        if (accounts.isEmpty() || mNewLocalProfile) {
             createContact(null);
             return;  // Don't show a dialog.
         }
@@ -559,6 +576,11 @@
         EntityModifier.ensureKindExists(insert, newAccountType, Event.CONTENT_ITEM_TYPE);
         EntityModifier.ensureKindExists(insert, newAccountType, StructuredPostal.CONTENT_ITEM_TYPE);
 
+        // Set the correct URI for saving the contact as a profile
+        if (mNewLocalProfile) {
+            insert.setProfileQueryUri();
+        }
+
         if (mState == null) {
             // Create state if none exists yet
             mState = EntityDeltaList.fromSingle(insert);
@@ -606,7 +628,7 @@
             if (Intent.ACTION_INSERT.equals(mAction) && numRawContacts == 1) {
                 final List<AccountWithDataSet> accounts =
                         AccountTypeManager.getInstance(mContext).getAccounts(true);
-                if (accounts.size() > 1) {
+                if (accounts.size() > 1 && !mNewLocalProfile) {
                     addAccountSwitcher(mState.get(0), editor);
                 } else {
                     disableAccountSwitcher(editor);
@@ -901,8 +923,8 @@
         setEnabled(false);
 
         Intent intent = ContactSaveService.createSaveContactIntent(getActivity(), mState,
-                SAVE_MODE_EXTRA_KEY, saveMode, getActivity().getClass(),
-                ContactEditorActivity.ACTION_SAVE_COMPLETED);
+                SAVE_MODE_EXTRA_KEY, saveMode, mNewLocalProfile || mIsUserProfile,
+                getActivity().getClass(), ContactEditorActivity.ACTION_SAVE_COMPLETED);
         getActivity().startService(intent);
         return true;
     }
@@ -1499,6 +1521,8 @@
         outState.putBoolean(KEY_CONTACT_WRITABLE_FOR_JOIN, mContactWritableForJoin);
         outState.putLong(KEY_SHOW_JOIN_SUGGESTIONS, mAggregationSuggestionsRawContactId);
         outState.putBoolean(KEY_ENABLED, mEnabled);
+        outState.putBoolean(KEY_NEW_LOCAL_PROFILE, mNewLocalProfile);
+        outState.putBoolean(KEY_IS_USER_PROFILE, mIsUserProfile);
         outState.putInt(KEY_STATUS, mStatus);
         super.onSaveInstanceState(outState);
     }
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index f95dea3..4ac54dc 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -35,6 +35,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.QuickContactBadge;
+import android.widget.SectionIndexer;
 import android.widget.TextView;
 
 import java.util.HashSet;
@@ -63,7 +64,17 @@
 
     private boolean mDisplayPhotos;
     private boolean mQuickContactEnabled;
+
+    /**
+     * indicates if contact queries include profile
+     */
     private boolean mIncludeProfile;
+
+    /**
+     * indicates if query results includes a profile
+     */
+    private boolean mProfileExists;
+
     private ContactPhotoManager mPhotoLoader;
 
     private String mQueryString;
@@ -78,6 +89,7 @@
     private boolean mSelectionVisible;
 
     private ContactListFilter mFilter;
+    private String mContactsCount = "";
 
     public ContactEntryListAdapter(Context context) {
         super(context);
@@ -94,6 +106,20 @@
         ((ContactListPinnedHeaderView)pinnedHeaderView).setSectionHeader(title);
     }
 
+    protected void setPinnedHeaderContactsCount(View header) {
+        // Update the header with the contacts count only if a profile header exists
+        // otherwise, the contacts count are shown in the empty profile header view
+        if (mProfileExists) {
+            ((ContactListPinnedHeaderView)header).setCountView(mContactsCount);
+        } else {
+            clearPinnedHeaderContactsCount(header);
+        }
+    }
+
+    protected void clearPinnedHeaderContactsCount(View header) {
+        ((ContactListPinnedHeaderView)header).setCountView(null);
+    }
+
     protected void addPartitions() {
         addPartition(createDefaultDirectoryPartition());
     }
@@ -278,6 +304,22 @@
         mIncludeProfile = includeProfile;
     }
 
+    public void setProfileExists(boolean exists) {
+        mProfileExists = exists;
+        // Stick the "ME" header for the profile
+        if (exists) {
+            SectionIndexer indexer = getIndexer();
+            if (indexer != null) {
+                ((ContactsSectionIndexer) indexer).setProfileHeader(
+                        getContext().getString(R.string.user_profile_contacts_list_header));
+            }
+        }
+    }
+
+    public boolean hasProfile() {
+        return mProfileExists;
+    }
+
     public void configureDirectoryLoader(DirectoryListLoader loader) {
         loader.setDirectorySearchMode(mDirectorySearchMode);
         loader.setLocalInvisibleDirectoryEnabled(LOCAL_INVISIBLE_DIRECTORY_ENABLED);
@@ -571,32 +613,6 @@
         mFilter = filter;
     }
 
-    @Override
-    public Placement getItemPlacementInSection(int position) {
-        // Special case code to prevent a section header from being displayed above the user's
-        // profile entry.
-        if (isUserProfile(position)) {
-            // The user profile entry shouldn't display a section header above; the header should be
-            // displayed on top of the item below.
-            Placement placement = new Placement();
-            placement.firstInSection = false;
-            placement.lastInSection = false;
-            placement.sectionHeader = null;
-            return placement;
-        } else if (position > 0 && isUserProfile(position - 1)) {
-            // If the item in the previous position is the user's profile, behave as if this entry
-            // is the first in the section.
-            Placement profilePlacement = super.getItemPlacementInSection(position - 1);
-            String profileHeader = profilePlacement.sectionHeader;
-            Placement placement = super.getItemPlacementInSection(position);
-            placement.firstInSection = true;
-            placement.sectionHeader = profileHeader;
-            return placement;
-        } else {
-            return super.getItemPlacementInSection(position);
-        }
-    }
-
     // TODO: move sharable logic (bindXX() methods) to here with extra arguments
 
     protected void bindQuickContact(final ContactListItemView view, int partitionIndex,
@@ -624,4 +640,12 @@
         }
         return uri;
     }
+
+    public void setContactsCount(String count) {
+        mContactsCount = count;
+    }
+
+    public String getContactsCount() {
+        return mContactsCount;
+    }
 }
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index 73d0859..4ddba75 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -134,6 +134,8 @@
 
     private boolean mForceLoad;
 
+    protected boolean mUserProfileExists;
+
     private static final int STATUS_NOT_LOADED = 0;
     private static final int STATUS_LOADING = 1;
     private static final int STATUS_LOADED = 2;
@@ -444,6 +446,7 @@
         }
 
         mAdapter.changeCursor(partitionIndex, data);
+        setProfileHeader();
         showCount(partitionIndex, data);
 
         if (!isLoading()) {
@@ -499,6 +502,14 @@
     }
 
     /**
+     * Shows a view at the top of the list with a pseudo local profile prompting the user to add
+     * a local profile. Default implementation does nothing.
+     */
+    protected void setProfileHeader() {
+        mUserProfileExists = false;
+    }
+
+    /**
      * Provides logic that dismisses this fragment. The default implementation
      * does nothing.
      */
@@ -585,6 +596,9 @@
 
     public void setIncludeProfile(boolean flag) {
         mIncludeProfile = flag;
+        if(mAdapter != null) {
+            mAdapter.setIncludeProfile(flag);
+        }
     }
 
     public void setSearchMode(boolean flag) {
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index 48fd342..51cc965 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -113,14 +113,12 @@
     private String mSelectedContactLookupKey;
     private long mSelectedContactId;
 
-    // View types for entries in the list view.
-    private final int mViewTypeProfileEntry;
+    private ContactListFilter mFilter;
 
     public ContactListAdapter(Context context) {
         super(context);
 
         mUnknownNameText = context.getText(R.string.missing_name);
-        mViewTypeProfileEntry = getViewTypeCount() - 1;
     }
 
     public CharSequence getUnknownNameText() {
@@ -226,12 +224,7 @@
     @Override
     protected View newView(Context context, int partition, Cursor cursor, int position,
             ViewGroup parent) {
-        ContactListItemView view;
-        if (getItemViewType(position) == mViewTypeProfileEntry) {
-            view = new ContactListProfileItemView(context, null);
-        } else {
-            view = new ContactListItemView(context, null);
-        }
+        ContactListItemView view = new ContactListItemView(context, null);
         view.setUnknownNameText(mUnknownNameText);
         view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
         view.setQuickContactEnabled(isQuickContactEnabled());
@@ -239,32 +232,23 @@
         return view;
     }
 
-    @Override
-    public int getItemViewType(int position) {
-        return isUserProfile(position)
-                ? mViewTypeProfileEntry
-                : super.getItemViewType(position);
-    }
-
-    @Override
-    public int getItemViewTypeCount() {
-        return super.getItemViewTypeCount() + 1;
-    }
-
-    @Override
-    public int getViewTypeCount() {
-        // One extra view type - the user's profile entry view.
-        return super.getViewTypeCount() + 1;
-    }
-
-    protected void bindSectionHeaderAndDivider(ContactListItemView view, int position) {
+    protected void bindSectionHeaderAndDivider(ContactListItemView view, int position,
+            Cursor cursor) {
         if (isSectionHeaderDisplayEnabled()) {
             Placement placement = getItemPlacementInSection(position);
-            view.setSectionHeader(placement.firstInSection ? placement.sectionHeader : null);
+
+            // First position, set the contacts number string
+            if (position == 0 && cursor.getInt(CONTACT_IS_USER_PROFILE) == 1) {
+                view.setCountView(getContactsCount());
+            } else {
+                view.setCountView(null);
+            }
+            view.setSectionHeader(placement.sectionHeader);
             view.setDividerVisible(!placement.lastInSection);
         } else {
             view.setSectionHeader(null);
             view.setDividerVisible(true);
+            view.setCountView(null);
         }
     }
 
@@ -384,4 +368,15 @@
 
         return null;
     }
+
+    @Override
+    public void changeCursor(int partitionIndex, Cursor cursor) {
+        super.changeCursor(partitionIndex, cursor);
+
+        // Check if a profile exists
+        if (cursor != null && cursor.getCount() > 0) {
+            cursor.moveToFirst();
+            setProfileExists(cursor.getInt(CONTACT_IS_USER_PROFILE) == 1);
+        }
+    }
 }
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index 4c20dca..0fe8d0d 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -115,6 +115,7 @@
     private TextView mDataView;
     private TextView mSnippetView;
     private TextView mStatusView;
+    private TextView mCountView;
     private ImageView mPresenceIcon;
 
     private char[] mHighlightedPrefix;
@@ -346,6 +347,11 @@
             mHeaderTextView.measure(
                     MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
+            if (mCountView != null) {
+                mCountView.measure(
+                        MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
+                        MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
+            }
             mHeaderBackgroundHeight = Math.max(mHeaderBackgroundHeight,
                     mHeaderTextView.getMeasuredHeight());
             height += (mHeaderBackgroundHeight + mHeaderUnderlineHeight);
@@ -370,6 +376,12 @@
                     0,
                     width - mPaddingRight,
                     mHeaderBackgroundHeight);
+            if (mCountView != null) {
+                mCountView.layout(width - mPaddingRight - mCountView.getMeasuredWidth(),
+                        0,
+                        width - mPaddingRight,
+                        mHeaderBackgroundHeight);
+            }
             mHeaderDivider.layout(leftBound,
                     mHeaderBackgroundHeight,
                     width - mPaddingRight,
@@ -921,6 +933,36 @@
     }
 
     /**
+     * Returns the text view for the contacts count, creating it if necessary.
+     */
+    public TextView getCountView() {
+        if (mCountView == null) {
+            mCountView = new TextView(mContext);
+            mCountView.setSingleLine(true);
+            mCountView.setEllipsize(getTextEllipsis());
+            mCountView.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
+            mCountView.setTextColor(R.color.contact_count_text_color);
+            addView(mCountView);
+        }
+        return mCountView;
+    }
+
+    /**
+     * Adds or updates a text view for the contacts count.
+     */
+    public void setCountView(CharSequence text) {
+        if (TextUtils.isEmpty(text)) {
+            if (mCountView != null) {
+                mCountView.setVisibility(View.GONE);
+            }
+        } else {
+            getCountView();
+            mCountView.setText(text);
+            mCountView.setVisibility(VISIBLE);
+        }
+    }
+
+    /**
      * Adds or updates a text view for the status.
      */
     public void setStatus(CharSequence text) {
diff --git a/src/com/android/contacts/list/ContactListPinnedHeaderView.java b/src/com/android/contacts/list/ContactListPinnedHeaderView.java
index 5d0d7b1..a689045 100644
--- a/src/com/android/contacts/list/ContactListPinnedHeaderView.java
+++ b/src/com/android/contacts/list/ContactListPinnedHeaderView.java
@@ -45,9 +45,11 @@
     private final int mHeaderUnderlineColor;
     private final int mPaddingRight;
     private final int mPaddingLeft;
+    private final int mContactsCountTextColor;
 
     private int mHeaderBackgroundHeight;
     private TextView mHeaderTextView;
+    private TextView mCountTextView = null;
     private View mHeaderDivider;
 
     public ContactListPinnedHeaderView(Context context, AttributeSet attrs) {
@@ -72,6 +74,8 @@
                 R.styleable.ContactListItemView_list_item_padding_left, 0);
         mPaddingRight = a.getDimensionPixelOffset(
                 R.styleable.ContactListItemView_list_item_padding_right, 0);
+        mContactsCountTextColor = a.getColor(
+                R.styleable.ContactListItemView_list_item_contacts_count_text_color, Color.BLACK);
 
         a.recycle();
 
@@ -93,8 +97,13 @@
         int width = resolveSize(0, widthMeasureSpec);
 
         mHeaderTextView.measure(
-                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
                 MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
+        if (isViewMeasurable(mCountTextView)) {
+            mCountTextView.measure(
+                    MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
+                    MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
+        }
 
         setMeasuredDimension(width, mHeaderBackgroundHeight + mHeaderUnderlineHeight);
     }
@@ -105,8 +114,16 @@
 
         mHeaderTextView.layout(mHeaderTextIndent + mPaddingLeft,
                 0,
-                width,
+                mHeaderTextView.getMeasuredWidth() + mHeaderTextIndent + mPaddingLeft,
                 mHeaderBackgroundHeight);
+
+        if (isViewMeasurable(mCountTextView)) {
+            mCountTextView.layout(width - mPaddingRight - mCountTextView.getMeasuredWidth(),
+                    0,
+                    width,
+                    mHeaderBackgroundHeight);
+        }
+
         mHeaderDivider.layout(mPaddingLeft,
                 mHeaderBackgroundHeight,
                 width,
@@ -134,4 +151,23 @@
         // view (ListView).
         forceLayout();
     }
+
+    public void setCountView(String count) {
+        if (mCountTextView == null) {
+            mCountTextView = new TextView(mContext);
+            mCountTextView.setTextColor(mContactsCountTextColor);
+            mCountTextView.setTextSize(mHeaderTextSize);
+            addView(mCountTextView);
+        }
+        mCountTextView.setText(count);
+        if (count == null || count.isEmpty()) {
+            mCountTextView.setVisibility(View.GONE);
+        } else {
+            mCountTextView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private boolean isViewMeasurable(View view) {
+        return (view != null && view.getVisibility() == View.VISIBLE);
+    }
 }
diff --git a/src/com/android/contacts/list/ContactListProfileItemView.java b/src/com/android/contacts/list/ContactListProfileItemView.java
deleted file mode 100644
index f189366..0000000
--- a/src/com/android/contacts/list/ContactListProfileItemView.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.list;
-
-import com.android.contacts.R;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.database.Cursor;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-/**
- * Contact list entry that represents the user's personal profile data.
- */
-public class ContactListProfileItemView extends ContactListItemView {
-
-    public ContactListProfileItemView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ContactListItemView);
-        setDefaultPhotoViewSize(a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_profile_photo_size, 0));
-    }
-
-    @Override
-    public TextView getNameTextView() {
-        TextView nameTextView = super.getNameTextView();
-        nameTextView.setTextAppearance(getContext(), android.R.style.TextAppearance_Large);
-        return nameTextView;
-    }
-
-    @Override
-    public void showDisplayName(Cursor cursor, int nameColumnIndex, int alternativeNameColumnIndex,
-            boolean highlightingEnabled, int displayOrder) {
-        getNameTextView().setText(getContext().getText(R.string.profile_display_name));
-    }
-}
diff --git a/src/com/android/contacts/list/ContactsSectionIndexer.java b/src/com/android/contacts/list/ContactsSectionIndexer.java
index a80d1de..109b8ba 100644
--- a/src/com/android/contacts/list/ContactsSectionIndexer.java
+++ b/src/com/android/contacts/list/ContactsSectionIndexer.java
@@ -93,4 +93,10 @@
          */
         return index >= 0 ? index : -index - 2;
     }
+
+    public void setProfileHeader(String header) {
+        if (mSections != null && mSections.length > 0) {
+            mSections[0] = header;
+        }
+    }
 }
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index b6a5be0..8590c29 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -16,13 +16,18 @@
 package com.android.contacts.list;
 
 import com.android.contacts.R;
+import com.android.contacts.editor.ContactEditorFragment;
 
+import android.content.Intent;
 import android.database.Cursor;
+import android.provider.ContactsContract.Contacts;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Button;
 import android.widget.FrameLayout;
+import android.widget.ListView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
@@ -36,6 +41,10 @@
     private View mSearchHeaderView;
     private TextView mAccountFilterHeaderView;
     private View mAccountFilterHeaderContainer;
+    private View mProfileHeader;
+    private Button mProfileMessage;
+    private FrameLayout mMessageContainer;
+    private View mProfileTitle;
 
     public DefaultContactBrowseListFragment() {
         setPhotoLoaderEnabled(true);
@@ -70,6 +79,11 @@
                 getView().findViewById(R.id.account_filter_header_container);
         mCounterHeaderView = (TextView) getView().findViewById(R.id.contacts_count);
 
+        // Create an empty user profile header and hide it for now (it will be visible if the
+        // contacts list will have no user profile).
+        addEmptyUserProfileHeader(inflater);
+        showEmptyUserProfile(false);
+
         // Putting the header view inside a container will allow us to make
         // it invisible later. See checkHeaderViewVisibility()
         FrameLayout headerContainer = new FrameLayout(inflater.getContext());
@@ -125,7 +139,11 @@
             if (count != 0) {
                 String format = getResources().getQuantityText(
                         R.plurals.listTotalAllContacts, count).toString();
-                mCounterHeaderView.setText(String.format(format, count));
+                if (mUserProfileExists) {
+                    getAdapter().setContactsCount(String.format(format, count));
+                } else {
+                    mCounterHeaderView.setText(String.format(format, count));
+                }
             } else {
                 ContactListFilter filter = getFilter();
                 int filterType = filter != null ? filter.filterType
@@ -176,6 +194,56 @@
                 }
                 mSearchHeaderView.setVisibility(View.VISIBLE);
             }
+            showEmptyUserProfile(false);
         }
     }
+
+    @Override
+    protected void setProfileHeader() {
+        mUserProfileExists = getAdapter().hasProfile();
+        showEmptyUserProfile(!mUserProfileExists && !isSearchMode());
+    }
+
+    private void showEmptyUserProfile(boolean show) {
+        // Changing visibility of just the mProfileHeader doesn't do anything unless
+        // you change visibility of its children, hence the call to mCounterHeaderView
+        // and mProfileTitle
+        mProfileHeader.setVisibility(show ? View.VISIBLE : View.GONE);
+        mCounterHeaderView.setVisibility(show ? View.VISIBLE : View.GONE);
+        mProfileTitle.setVisibility(show ? View.VISIBLE : View.GONE);
+        mMessageContainer.setVisibility(show ? View.VISIBLE : View.GONE);
+        mProfileMessage.setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+
+    /**
+     * This method creates a pseudo user profile contact. When the returned query doesn't have
+     * a profile, this methods creates 2 views that are inserted as headers to the listview:
+     * 1. A header view with the "ME" title and the contacts count.
+     * 2. A button that prompts the user to create a local profile
+     */
+    private void addEmptyUserProfileHeader(LayoutInflater inflater) {
+
+        ListView list = getListView();
+        // Put a header with the "ME" name and a view for the number of contacts
+        mProfileHeader = inflater.inflate(R.layout.user_profile_header, null, false);
+        mCounterHeaderView = (TextView) mProfileHeader.findViewById(R.id.contacts_count);
+        mProfileTitle = mProfileHeader.findViewById(R.id.profile_title);
+        list.addHeaderView(mProfileHeader, null, false);
+
+        // Add a selectable view with a message inviting the user to create a local profile
+        // The view is embedded in a frame view since you cannot change the visibility of a
+        // view in a ListView without having a parent view.
+        mMessageContainer = new FrameLayout(inflater.getContext());
+        mProfileMessage = (Button)inflater.inflate(R.layout.user_profile_button, null, false);
+        mMessageContainer.addView(mProfileMessage);
+        list.addHeaderView(mMessageContainer, null, true);
+
+        mProfileMessage.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+                intent.putExtra(ContactEditorFragment.INTENT_EXTRA_NEW_LOCAL_PROFILE, true);
+                startActivity(intent);
+            }
+        });
+    }
 }
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index 8e96690..e9804f5 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -229,7 +229,7 @@
             view.setActivated(isSelectedContact(partition, cursor));
         }
 
-        bindSectionHeaderAndDivider(view, position);
+        bindSectionHeaderAndDivider(view, position, cursor);
 
         if (isQuickContactEnabled()) {
             bindQuickContact(view, partition, cursor,
diff --git a/src/com/android/contacts/list/JoinContactListAdapter.java b/src/com/android/contacts/list/JoinContactListAdapter.java
index e7a9eb9..e764125 100644
--- a/src/com/android/contacts/list/JoinContactListAdapter.java
+++ b/src/com/android/contacts/list/JoinContactListAdapter.java
@@ -217,7 +217,7 @@
             }
             case PARTITION_ALL_CONTACTS: {
                 final ContactListItemView view = (ContactListItemView)itemView;
-                bindSectionHeaderAndDivider(view, position);
+                bindSectionHeaderAndDivider(view, position, cursor);
                 bindPhoto(view, partition, cursor);
                 bindName(view, cursor);
                 break;
diff --git a/src/com/android/contacts/model/EntityDelta.java b/src/com/android/contacts/model/EntityDelta.java
index fe084f4..97ab347 100644
--- a/src/com/android/contacts/model/EntityDelta.java
+++ b/src/com/android/contacts/model/EntityDelta.java
@@ -31,6 +31,7 @@
 import android.provider.BaseColumns;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
 import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Profile;
 import android.provider.ContactsContract.RawContacts;
 import android.util.Log;
 
@@ -66,6 +67,12 @@
     private ValuesDelta mValues;
 
     /**
+     * URI used for contacts queries, by default it is set to query raw contacts.
+     * It can be set to query the profile's raw contact(s).
+     */
+    private Uri mContactsQueryUri = RawContacts.CONTENT_URI;
+
+    /**
      * Internal map of children values from {@link Entity#getSubValues()}, which
      * we store here sorted into {@link Data#MIMETYPE} bins.
      */
@@ -367,7 +374,7 @@
             if (beforeId == null || beforeVersion == null) return;
 
             final ContentProviderOperation.Builder builder = ContentProviderOperation
-                    .newAssertQuery(RawContacts.CONTENT_URI);
+                    .newAssertQuery(mContactsQueryUri);
             builder.withSelection(RawContacts._ID + "=" + beforeId, null);
             builder.withValue(RawContacts.VERSION, beforeVersion);
             buildInto.add(builder.build());
@@ -398,7 +405,7 @@
         }
 
         // Build possible operation at Contact level
-        builder = mValues.buildDiff(RawContacts.CONTENT_URI);
+        builder = mValues.buildDiff(mContactsQueryUri);
         possibleAdd(buildInto, builder);
 
         // Build operations for all children
@@ -435,7 +442,7 @@
             buildInto.add(builder.build());
         } else if (isContactInsert) {
             // Restore aggregation mode as last operation
-            builder = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI);
+            builder = ContentProviderOperation.newUpdate(mContactsQueryUri);
             builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT);
             builder.withSelection(RawContacts._ID + "=?", new String[1]);
             builder.withSelectionBackReference(0, firstIndex);
@@ -448,7 +455,7 @@
      * {@link RawContacts#AGGREGATION_MODE} to the given value.
      */
     protected Builder buildSetAggregationMode(Long beforeId, int mode) {
-        Builder builder = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI);
+        Builder builder = ContentProviderOperation.newUpdate(mContactsQueryUri);
         builder.withValue(RawContacts.AGGREGATION_MODE, mode);
         builder.withSelection(RawContacts._ID + "=" + beforeId, null);
         return builder;
@@ -465,6 +472,7 @@
         final int size = this.getEntryCount(false);
         dest.writeInt(size);
         dest.writeParcelable(mValues, flags);
+        dest.writeParcelable(mContactsQueryUri, flags);
         for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
             for (ValuesDelta child : mimeEntries) {
                 dest.writeParcelable(child, flags);
@@ -476,12 +484,20 @@
         final ClassLoader loader = getClass().getClassLoader();
         final int size = source.readInt();
         mValues = source.<ValuesDelta> readParcelable(loader);
+        mContactsQueryUri = source.<Uri> readParcelable(loader);
         for (int i = 0; i < size; i++) {
             final ValuesDelta child = source.<ValuesDelta> readParcelable(loader);
             this.addEntry(child);
         }
     }
 
+    /**
+     * Used to set the query URI to the profile URI to store profiles.
+     */
+    public void setProfileQueryUri() {
+        mContactsQueryUri = Profile.CONTENT_RAW_CONTACTS_URI;
+    }
+
     public static final Parcelable.Creator<EntityDelta> CREATOR = new Parcelable.Creator<EntityDelta>() {
         public EntityDelta createFromParcel(Parcel in) {
             final EntityDelta state = new EntityDelta();
diff --git a/src/com/android/contacts/widget/IndexerListAdapter.java b/src/com/android/contacts/widget/IndexerListAdapter.java
index 4cd7af0..a5aeb0f 100644
--- a/src/com/android/contacts/widget/IndexerListAdapter.java
+++ b/src/com/android/contacts/widget/IndexerListAdapter.java
@@ -70,6 +70,16 @@
      */
     protected abstract void setPinnedSectionTitle(View pinnedHeaderView, String title);
 
+    /**
+     * Sets the contacts count in the pinned header.
+     */
+    protected abstract void setPinnedHeaderContactsCount(View header);
+
+    /**
+     * clears the contacts count in the pinned header and makes the view invisible.
+     */
+    protected abstract void clearPinnedHeaderContactsCount(View header);
+
     public boolean isSectionHeaderDisplayEnabled() {
         return mSectionHeaderDisplayEnabled;
     }
@@ -174,7 +184,11 @@
                 listView.setHeaderInvisible(index, false);
             } else {
                 setPinnedSectionTitle(mHeader, (String)mIndexer.getSections()[section]);
-
+                if (section == 0) {
+                    setPinnedHeaderContactsCount(mHeader);
+                } else {
+                    clearPinnedHeaderContactsCount(mHeader);
+                }
                 // Compute the item position where the current partition begins
                 int partitionStart = getPositionForPartition(mIndexedPartition);
                 if (hasHeader(mIndexedPartition)) {