Contacts UI changes for profiles.
The user's profile entry is displayed slightly differently from other Contacts.
The profile photo is 25% larger (e.g. 56dip -> 70dip) and rather than showing
the display name, it displays "My profile".
Section headers are also special-cased for the profile entry - it appears above
the section header rather than below it.
Change-Id: I6fd99d303c7dd1347031d3607741827ea2a31a0b
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 47340a0..d298fbe 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -25,6 +25,8 @@
<uses-permission android:name="android.permission.CALL_PRIVILEGED" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
+ <uses-permission android:name="android.permission.READ_PROFILE" />
+ <uses-permission android:name="android.permission.WRITE_PROFILE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
diff --git a/res/values-xlarge/styles.xml b/res/values-xlarge/styles.xml
index f73e51d..cbef91a 100644
--- a/res/values-xlarge/styles.xml
+++ b/res/values-xlarge/styles.xml
@@ -31,6 +31,7 @@
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">30dip</item>
<item name="list_item_photo_size">64dip</item>
+ <item name="list_item_profile_photo_size">80dip</item>
<item name="list_item_prefix_highlight_color">#729a27</item>
<item name="list_item_header_text_indent">77dip</item>
<item name="list_item_header_text_color">?color/section_header_text_color</item>
@@ -53,6 +54,7 @@
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">30dip</item>
<item name="list_item_photo_size">64dip</item>
+ <item name="list_item_profile_photo_size">80dip</item>
<item name="list_item_header_text_indent">77dip</item>
<item name="list_item_header_text_color">?color/section_header_text_color</item>
<item name="list_item_header_text_size">14sp</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 52eab2f..f259ea7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1537,4 +1537,8 @@
<string name="call_type_and_date">
<xliff:g id="call_type" example="Friends">%1$s</xliff:g> <xliff:g id="call_short_date" example="Friends">%2$s</xliff:g>
</string>
+
+ <!-- Text displayed in place of the display name for the contact that represents the user's
+ personal profile entry [CHAR LIMIT=64] -->
+ <string name="profile_display_name">My profile</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 509180e..36f7ca8 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -31,6 +31,7 @@
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">5dip</item>
<item name="list_item_photo_size">56dip</item>
+ <item name="list_item_profile_photo_size">70dip</item>
<item name="list_item_prefix_highlight_color">#729a27</item>
<item name="list_item_header_text_indent">56dip</item>
<item name="list_item_header_text_color">?color/section_header_text_color</item>
@@ -114,6 +115,7 @@
<attr name="list_item_vertical_divider_margin" format="dimension"/>
<attr name="list_item_presence_icon_margin" format="dimension"/>
<attr name="list_item_photo_size" format="dimension"/>
+ <attr name="list_item_profile_photo_size" format="dimension"/>
<attr name="list_item_prefix_highlight_color" format="color"/>
<attr name="list_item_header_text_indent" format="dimension" />
<attr name="list_item_header_text_color" format="color" />
@@ -153,6 +155,7 @@
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">5dip</item>
<item name="list_item_photo_size">56dip</item>
+ <item name="list_item_profile_photo_size">70dip</item>
<item name="list_item_prefix_highlight_color">#729a27</item>
<item name="list_item_header_text_indent">56dip</item>
<item name="list_item_header_text_color">?color/section_header_text_color</item>
@@ -178,6 +181,7 @@
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">5dip</item>
<item name="list_item_photo_size">56dip</item>
+ <item name="list_item_profile_photo_size">70dip</item>
<item name="list_item_prefix_highlight_color">#729a27</item>
<item name="list_item_header_text_indent">56dip</item>
<item name="list_item_header_text_color">?color/section_header_text_color</item>
@@ -198,6 +202,7 @@
<item name="list_item_vertical_divider_margin">5dip</item>
<item name="list_item_presence_icon_margin">5dip</item>
<item name="list_item_photo_size">56dip</item>
+ <item name="list_item_profile_photo_size">70dip</item>
<item name="list_item_prefix_highlight_color">#729a27</item>
<item name="list_item_header_text_indent">56dip</item>
<item name="list_item_header_text_color">?color/section_header_text_color</item>
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index c255ba1..c63f76a 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -217,6 +217,7 @@
case ContactsRequest.ACTION_PICK_CONTACT: {
ContactPickerFragment fragment = new ContactPickerFragment();
fragment.setSearchMode(mRequest.isSearchMode());
+ fragment.setIncludeProfile(mRequest.shouldIncludeProfile());
mListFragment = fragment;
break;
}
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index 7f7dad4..fc9abe7 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -386,6 +386,9 @@
mSelectedContactDirectoryId, mSelectedContactLookupKey, mSelectedContactId);
}
}
+
+ // Display the user's profile.
+ adapter.setIncludeProfile(true);
}
@Override
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index a2b264b..95a8c2b 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.provider.ContactsContract;
import android.provider.ContactsContract.ContactCounts;
+import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Directory;
import android.text.TextUtils;
import android.view.LayoutInflater;
@@ -62,6 +63,7 @@
private boolean mDisplayPhotos;
private boolean mQuickContactEnabled;
+ private boolean mIncludeProfile;
private ContactPhotoManager mPhotoLoader;
private String mQueryString;
@@ -266,6 +268,14 @@
mQuickContactEnabled = quickContactEnabled;
}
+ public boolean shouldIncludeProfile() {
+ return mIncludeProfile;
+ }
+
+ public void setIncludeProfile(boolean includeProfile) {
+ mIncludeProfile = includeProfile;
+ }
+
public boolean isDataRestrictedByCallingPackage() {
return mDataRestrictedByCallingPackage;
}
@@ -411,7 +421,9 @@
@Override
public int getItemViewType(int partitionIndex, int position) {
int type = super.getItemViewType(partitionIndex, position);
- if (isSectionHeaderDisplayEnabled() && partitionIndex == getIndexedPartition()) {
+ if (!isUserProfile(position)
+ && isSectionHeaderDisplayEnabled()
+ && partitionIndex == getIndexedPartition()) {
Placement placement = getItemPlacementInSection(position);
return placement.firstInSection ? type : getItemViewTypeCount() + type;
} else {
@@ -526,6 +538,33 @@
}
}
+ /**
+ * Checks whether the contact entry at the given position represents the user's profile.
+ */
+ protected boolean isUserProfile(int position) {
+ // The profile only ever appears in the first position if it is present. So if the position
+ // is anything beyond 0, it can't be the profile.
+ boolean isUserProfile = false;
+ if (position == 0) {
+ int partition = getPartitionForPosition(position);
+ if (partition >= 0) {
+ // Save the old cursor position - the call to getItem() may modify the cursor
+ // position.
+ int offset = getCursor(partition).getPosition();
+ Cursor cursor = (Cursor) getItem(position);
+ if (cursor != null) {
+ int profileColumnIndex = cursor.getColumnIndex(Contacts.IS_USER_PROFILE);
+ if (profileColumnIndex != -1) {
+ isUserProfile = cursor.getInt(profileColumnIndex) == 1;
+ }
+ // Restore the old cursor position.
+ cursor.moveToPosition(offset);
+ }
+ }
+ }
+ return isUserProfile;
+ }
+
// TODO: fix PluralRules to handle zero correctly and use Resources.getQuantityText directly
public String getQuantityText(int count, int zeroResourceId, int pluralResourceId) {
if (count == 0) {
@@ -544,4 +583,30 @@
}
return true;
}
+
+ @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);
+ }
+ }
}
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index a080b37..0937114 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -78,6 +78,7 @@
private static final String KEY_SECTION_HEADER_DISPLAY_ENABLED = "sectionHeaderDisplayEnabled";
private static final String KEY_PHOTO_LOADER_ENABLED = "photoLoaderEnabled";
private static final String KEY_QUICK_CONTACT_ENABLED = "quickContactEnabled";
+ private static final String KEY_INCLUDE_PROFILE = "includeProfile";
private static final String KEY_SEARCH_MODE = "searchMode";
private static final String KEY_VISIBLE_SCROLLBAR_ENABLED = "visibleScrollbarEnabled";
private static final String KEY_SCROLLBAR_POSITION = "scrollbarPosition";
@@ -100,6 +101,7 @@
private boolean mSectionHeaderDisplayEnabled;
private boolean mPhotoLoaderEnabled;
private boolean mQuickContactEnabled = true;
+ private boolean mIncludeProfile;
private boolean mSearchMode;
private boolean mVisibleScrollbarEnabled;
private int mVerticalScrollbarPosition = View.SCROLLBAR_POSITION_RIGHT;
@@ -235,6 +237,7 @@
outState.putBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED, mSectionHeaderDisplayEnabled);
outState.putBoolean(KEY_PHOTO_LOADER_ENABLED, mPhotoLoaderEnabled);
outState.putBoolean(KEY_QUICK_CONTACT_ENABLED, mQuickContactEnabled);
+ outState.putBoolean(KEY_INCLUDE_PROFILE, mIncludeProfile);
outState.putBoolean(KEY_SEARCH_MODE, mSearchMode);
outState.putBoolean(KEY_VISIBLE_SCROLLBAR_ENABLED, mVisibleScrollbarEnabled);
outState.putInt(KEY_SCROLLBAR_POSITION, mVerticalScrollbarPosition);
@@ -266,6 +269,7 @@
mSectionHeaderDisplayEnabled = savedState.getBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED);
mPhotoLoaderEnabled = savedState.getBoolean(KEY_PHOTO_LOADER_ENABLED);
mQuickContactEnabled = savedState.getBoolean(KEY_QUICK_CONTACT_ENABLED);
+ mIncludeProfile = savedState.getBoolean(KEY_INCLUDE_PROFILE);
mSearchMode = savedState.getBoolean(KEY_SEARCH_MODE);
mVisibleScrollbarEnabled = savedState.getBoolean(KEY_VISIBLE_SCROLLBAR_ENABLED);
mVerticalScrollbarPosition = savedState.getInt(KEY_SCROLLBAR_POSITION);
@@ -579,6 +583,10 @@
this.mQuickContactEnabled = flag;
}
+ public void setIncludeProfile(boolean flag) {
+ mIncludeProfile = flag;
+ }
+
public void setSearchMode(boolean flag) {
if (mSearchMode != flag) {
mSearchMode = flag;
@@ -782,6 +790,7 @@
}
mAdapter.setQuickContactEnabled(mQuickContactEnabled);
+ mAdapter.setIncludeProfile(mIncludeProfile);
mAdapter.setQueryString(mQueryString);
mAdapter.setDirectorySearchMode(mDirectorySearchMode);
mAdapter.setPinnedPartitionHeadersEnabled(mSearchMode);
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index 2c57983..cc4bea5 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -23,6 +23,7 @@
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.Profile;
import android.provider.ContactsContract.SearchSnippetColumns;
import android.text.TextUtils;
import android.view.View;
@@ -30,9 +31,10 @@
import android.widget.ListView;
import android.widget.QuickContactBadge;
-
/**
* A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
+ * Also includes support for including the {@link ContactsContract.Profile} record in the
+ * list.
*/
public abstract class ContactListAdapter extends ContactEntryListAdapter {
@@ -49,6 +51,7 @@
Contacts.LOOKUP_KEY, // 9
Contacts.PHONETIC_NAME, // 10
Contacts.HAS_PHONE_NUMBER, // 11
+ Contacts.IS_USER_PROFILE, // 12
};
protected static final String[] PROJECTION_DATA = new String[] {
@@ -79,7 +82,8 @@
Contacts.LOOKUP_KEY, // 9
Contacts.PHONETIC_NAME, // 10
Contacts.HAS_PHONE_NUMBER, // 11
- SearchSnippetColumns.SNIPPET, // 12
+ Contacts.IS_USER_PROFILE, // 12
+ SearchSnippetColumns.SNIPPET, // 13
};
protected static final int CONTACT_ID_COLUMN_INDEX = 0;
@@ -94,7 +98,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_SNIPPET_COLUMN_INDEX = 12;
+ protected static final int CONTACT_IS_USER_PROFILE = 12;
+ protected static final int CONTACT_SNIPPET_COLUMN_INDEX = 13;
private CharSequence mUnknownNameText;
private int mDisplayNameColumnIndex;
@@ -106,10 +111,15 @@
private ContactListFilter mFilter;
+ // View types for entries in the list view.
+ private final int mViewTypeProfileEntry;
+
+
public ContactListAdapter(Context context) {
super(context);
mUnknownNameText = context.getText(android.R.string.unknownName);
+ mViewTypeProfileEntry = getViewTypeCount() - 1;
}
public CharSequence getUnknownNameText() {
@@ -150,6 +160,11 @@
.appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
}
+ protected static Uri includeProfileEntry(Uri uri) {
+ return uri.buildUpon()
+ .appendQueryParameter(ContactsContract.INCLUDE_PROFILE, "true").build();
+ }
+
public boolean getHasPhoneNumber(int position) {
return ((Cursor)getItem(position)).getInt(CONTACT_HAS_PHONE_COLUMN_INDEX) != 0;
}
@@ -221,7 +236,12 @@
@Override
protected View newView(Context context, int partition, Cursor cursor, int position,
ViewGroup parent) {
- final ContactListItemView view = new ContactListItemView(context, null);
+ ContactListItemView view;
+ if (getItemViewType(position) == mViewTypeProfileEntry) {
+ view = new ContactListProfileItemView(context, null);
+ } else {
+ view = new ContactListItemView(context, null);
+ }
view.setUnknownNameText(mUnknownNameText);
view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
view.setQuickContactEnabled(isQuickContactEnabled());
@@ -229,6 +249,24 @@
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) {
if (isSectionHeaderDisplayEnabled()) {
Placement placement = getItemPlacementInSection(position);
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index bc81cd3..032b60f 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -472,11 +472,19 @@
ViewGroup.LayoutParams.WRAP_CONTENT);
a.recycle();
} else {
- mPhotoViewWidth = mPhotoViewHeight = mDefaultPhotoViewSize;
+ mPhotoViewWidth = mPhotoViewHeight = getDefaultPhotoViewSize();
}
}
}
+ protected void setDefaultPhotoViewSize(int pixels) {
+ mDefaultPhotoViewSize = pixels;
+ }
+
+ protected int getDefaultPhotoViewSize() {
+ return mDefaultPhotoViewSize;
+ }
+
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
diff --git a/src/com/android/contacts/list/ContactListProfileItemView.java b/src/com/android/contacts/list/ContactListProfileItemView.java
new file mode 100644
index 0000000..f189366
--- /dev/null
+++ b/src/com/android/contacts/list/ContactListProfileItemView.java
@@ -0,0 +1,51 @@
+/*
+ * 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/ContactsRequest.java b/src/com/android/contacts/list/ContactsRequest.java
index e20d189..469cd1d 100644
--- a/src/com/android/contacts/list/ContactsRequest.java
+++ b/src/com/android/contacts/list/ContactsRequest.java
@@ -83,6 +83,7 @@
private CharSequence mTitle;
private boolean mSearchMode;
private String mQueryString;
+ private boolean mIncludeProfile;
private String mGroupName;
private boolean mLegacyCompatibilityMode;
private boolean mDirectorySearchEnabled = true;
@@ -98,6 +99,7 @@
mTitle = request.mTitle;
mSearchMode = request.mSearchMode;
mQueryString = request.mQueryString;
+ mIncludeProfile = request.mIncludeProfile;
mGroupName = request.mGroupName;
mLegacyCompatibilityMode = request.mLegacyCompatibilityMode;
mDirectorySearchEnabled = request.mDirectorySearchEnabled;
@@ -119,6 +121,7 @@
request.mTitle = source.readCharSequence();
request.mSearchMode = source.readInt() != 0;
request.mQueryString = source.readString();
+ request.mIncludeProfile = source.readInt() != 0;
request.mGroupName = source.readString();
request.mLegacyCompatibilityMode = source.readInt() != 0;
request.mDirectorySearchEnabled = source.readInt() != 0;
@@ -134,6 +137,7 @@
dest.writeCharSequence(mTitle);
dest.writeInt(mSearchMode ? 1 : 0);
dest.writeString(mQueryString);
+ dest.writeInt(mIncludeProfile ? 1 : 0);
dest.writeString(mGroupName);
dest.writeInt(mLegacyCompatibilityMode ? 1 : 0);
dest.writeInt(mDirectorySearchEnabled ? 1 : 0);
@@ -192,6 +196,14 @@
mQueryString = string;
}
+ public boolean shouldIncludeProfile() {
+ return mIncludeProfile;
+ }
+
+ public void setIncludeProfile(boolean includeProfile) {
+ mIncludeProfile = includeProfile;
+ }
+
public String getGroupName() {
return mGroupName;
}
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index 3e8c6f1..6916514 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -131,6 +131,11 @@
}
uri = applyDataRestriction(uri);
+ // Include the user's personal profile.
+ if (shouldIncludeProfile()) {
+ uri = includeProfileEntry(uri);
+ }
+
loader.setUri(uri);
}