Moving ContactListItemView and dependent classes.
Further clean-up of PhoneFavoriteFragment in Dialer app to move all necessary
dependencies into Contacts Common package.
Bug: 6993891
Change-Id: I3dfce84ad01932450dc09588c22903e7307d7da0
diff --git a/src/com/android/contacts/GroupMemberLoader.java b/src/com/android/contacts/GroupMemberLoader.java
index 12e82ce..e52ddda 100644
--- a/src/com/android/contacts/GroupMemberLoader.java
+++ b/src/com/android/contacts/GroupMemberLoader.java
@@ -24,7 +24,7 @@
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Directory;
-import com.android.contacts.preference.ContactsPreferences;
+import com.android.contacts.common.preference.ContactsPreferences;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index c60a228..6822494 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -46,7 +46,7 @@
import com.android.contacts.list.ContactPickerFragment;
import com.android.contacts.list.ContactsIntentResolver;
import com.android.contacts.list.ContactsRequest;
-import com.android.contacts.list.DirectoryListLoader;
+import com.android.contacts.common.list.DirectoryListLoader;
import com.android.contacts.list.EmailAddressPickerFragment;
import com.android.contacts.list.OnContactPickerActionListener;
import com.android.contacts.list.OnEmailAddressPickerActionListener;
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index b607408..792348f 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -65,7 +65,7 @@
import com.android.contacts.interactions.ImportExportDialogFragment;
import com.android.contacts.list.ContactBrowseListFragment;
import com.android.contacts.list.ContactEntryListFragment;
-import com.android.contacts.list.ContactListFilter;
+import com.android.contacts.common.list.ContactListFilter;
import com.android.contacts.list.ContactListFilterController;
import com.android.contacts.list.ContactTileAdapter.DisplayType;
import com.android.contacts.list.ContactTileFrequentFragment;
@@ -74,7 +74,7 @@
import com.android.contacts.list.ContactsRequest;
import com.android.contacts.list.ContactsUnavailableFragment;
import com.android.contacts.list.DefaultContactBrowseListFragment;
-import com.android.contacts.list.DirectoryListLoader;
+import com.android.contacts.common.list.DirectoryListLoader;
import com.android.contacts.list.OnContactBrowserActionListener;
import com.android.contacts.list.OnContactsUnavailableActionListener;
import com.android.contacts.list.ProviderStatusWatcher;
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index bda4918..12b63c7 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -45,7 +45,7 @@
import com.android.contacts.model.RawContact;
import com.android.contacts.model.dataitem.DataItem;
import com.android.contacts.model.dataitem.OrganizationDataItem;
-import com.android.contacts.preference.ContactsPreferences;
+import com.android.contacts.common.preference.ContactsPreferences;
import com.android.contacts.util.ContactBadgeUtil;
import com.android.contacts.util.HtmlUtils;
import com.android.contacts.util.MoreMath;
diff --git a/src/com/android/contacts/group/GroupBrowseListFragment.java b/src/com/android/contacts/group/GroupBrowseListFragment.java
index 625615f..d6ca3d7 100644
--- a/src/com/android/contacts/group/GroupBrowseListFragment.java
+++ b/src/com/android/contacts/group/GroupBrowseListFragment.java
@@ -46,7 +46,7 @@
import com.android.contacts.GroupListLoader;
import com.android.contacts.R;
import com.android.contacts.group.GroupBrowseListAdapter.GroupListItemViewCache;
-import com.android.contacts.widget.AutoScrollListView;
+import com.android.contacts.common.list.AutoScrollListView;
/**
* Fragment to display the list of groups.
diff --git a/src/com/android/contacts/list/AccountFilterActivity.java b/src/com/android/contacts/list/AccountFilterActivity.java
index af727a5..18d703f 100644
--- a/src/com/android/contacts/list/AccountFilterActivity.java
+++ b/src/com/android/contacts/list/AccountFilterActivity.java
@@ -36,6 +36,7 @@
import com.android.contacts.ContactsActivity;
import com.android.contacts.R;
+import com.android.contacts.common.list.ContactListFilter;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.account.AccountType;
import com.android.contacts.model.account.AccountWithDataSet;
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index da4a644..8ea8c1c 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -36,8 +36,11 @@
import com.android.common.widget.CompositeCursorAdapter.Partition;
import com.android.contacts.R;
+import com.android.contacts.common.list.ContactListAdapter;
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.list.DirectoryPartition;
import com.android.contacts.util.ContactLoaderUtils;
-import com.android.contacts.widget.AutoScrollListView;
+import com.android.contacts.common.list.AutoScrollListView;
import java.util.List;
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
deleted file mode 100644
index 1f0373f..0000000
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ /dev/null
@@ -1,678 +0,0 @@
-/*
- * Copyright (C) 2010 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 android.content.Context;
-import android.content.CursorLoader;
-import android.database.Cursor;
-import android.net.Uri;
-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.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.QuickContactBadge;
-import android.widget.SectionIndexer;
-import android.widget.TextView;
-
-import com.android.contacts.common.ContactPhotoManager;
-import com.android.contacts.R;
-import com.android.contacts.widget.IndexerListAdapter;
-
-import java.util.HashSet;
-
-/**
- * Common base class for various contact-related lists, e.g. contact list, phone number list
- * etc.
- */
-public abstract class ContactEntryListAdapter extends IndexerListAdapter {
-
- private static final String TAG = "ContactEntryListAdapter";
-
- /**
- * Indicates whether the {@link Directory#LOCAL_INVISIBLE} directory should
- * be included in the search.
- */
- private static final boolean LOCAL_INVISIBLE_DIRECTORY_ENABLED = false;
-
- private int mDisplayOrder;
- private int mSortOrder;
-
- 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;
- private char[] mUpperCaseQueryString;
- private boolean mSearchMode;
- private int mDirectorySearchMode;
- private int mDirectoryResultLimit = Integer.MAX_VALUE;
-
- private boolean mLoading = true;
- private boolean mEmptyListEnabled = true;
-
- private boolean mSelectionVisible;
-
- private ContactListFilter mFilter;
- private String mContactsCount = "";
- private boolean mDarkTheme = false;
-
- /** Resource used to provide header-text for default filter. */
- private CharSequence mDefaultFilterHeaderText;
-
- public ContactEntryListAdapter(Context context) {
- super(context);
- addPartitions();
- setDefaultFilterHeaderText(R.string.local_search_label);
- }
-
- protected void setDefaultFilterHeaderText(int resourceId) {
- mDefaultFilterHeaderText = getContext().getResources().getText(resourceId);
- }
-
- @Override
- protected View createPinnedSectionHeaderView(Context context, ViewGroup parent) {
- return new ContactListPinnedHeaderView(context, null);
- }
-
- @Override
- protected void setPinnedSectionTitle(View pinnedHeaderView, String title) {
- ((ContactListPinnedHeaderView)pinnedHeaderView).setSectionHeader(title);
- }
-
- @Override
- 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);
- }
- }
-
- @Override
- protected void clearPinnedHeaderContactsCount(View header) {
- ((ContactListPinnedHeaderView)header).setCountView(null);
- }
-
- protected void addPartitions() {
- addPartition(createDefaultDirectoryPartition());
- }
-
- protected DirectoryPartition createDefaultDirectoryPartition() {
- DirectoryPartition partition = new DirectoryPartition(true, true);
- partition.setDirectoryId(Directory.DEFAULT);
- partition.setDirectoryType(getContext().getString(R.string.contactsList));
- partition.setPriorityDirectory(true);
- partition.setPhotoSupported(true);
- return partition;
- }
-
- /**
- * Remove all directories after the default directory. This is typically used when contacts
- * list screens are asked to exit the search mode and thus need to remove all remote directory
- * results for the search.
- *
- * This code assumes that the default directory and directories before that should not be
- * deleted (e.g. Join screen has "suggested contacts" directory before the default director,
- * and we should not remove the directory).
- */
- /* package */ void removeDirectoriesAfterDefault() {
- final int partitionCount = getPartitionCount();
- for (int i = partitionCount - 1; i >= 0; i--) {
- final Partition partition = getPartition(i);
- if ((partition instanceof DirectoryPartition)
- && ((DirectoryPartition) partition).getDirectoryId() == Directory.DEFAULT) {
- break;
- } else {
- removePartition(i);
- }
- }
- }
-
- private int getPartitionByDirectoryId(long id) {
- int count = getPartitionCount();
- for (int i = 0; i < count; i++) {
- Partition partition = getPartition(i);
- if (partition instanceof DirectoryPartition) {
- if (((DirectoryPartition)partition).getDirectoryId() == id) {
- return i;
- }
- }
- }
- return -1;
- }
-
- public abstract String getContactDisplayName(int position);
- public abstract void configureLoader(CursorLoader loader, long directoryId);
-
- /**
- * Marks all partitions as "loading"
- */
- public void onDataReload() {
- boolean notify = false;
- int count = getPartitionCount();
- for (int i = 0; i < count; i++) {
- Partition partition = getPartition(i);
- if (partition instanceof DirectoryPartition) {
- DirectoryPartition directoryPartition = (DirectoryPartition)partition;
- if (!directoryPartition.isLoading()) {
- notify = true;
- }
- directoryPartition.setStatus(DirectoryPartition.STATUS_NOT_LOADED);
- }
- }
- if (notify) {
- notifyDataSetChanged();
- }
- }
-
- @Override
- public void clearPartitions() {
- int count = getPartitionCount();
- for (int i = 0; i < count; i++) {
- Partition partition = getPartition(i);
- if (partition instanceof DirectoryPartition) {
- DirectoryPartition directoryPartition = (DirectoryPartition)partition;
- directoryPartition.setStatus(DirectoryPartition.STATUS_NOT_LOADED);
- }
- }
- super.clearPartitions();
- }
-
- public boolean isSearchMode() {
- return mSearchMode;
- }
-
- public void setSearchMode(boolean flag) {
- mSearchMode = flag;
- }
-
- public String getQueryString() {
- return mQueryString;
- }
-
- public void setQueryString(String queryString) {
- mQueryString = queryString;
- if (TextUtils.isEmpty(queryString)) {
- mUpperCaseQueryString = null;
- } else {
- mUpperCaseQueryString = queryString.toUpperCase().toCharArray();
- }
- }
-
- public char[] getUpperCaseQueryString() {
- return mUpperCaseQueryString;
- }
-
- public int getDirectorySearchMode() {
- return mDirectorySearchMode;
- }
-
- public void setDirectorySearchMode(int mode) {
- mDirectorySearchMode = mode;
- }
-
- public int getDirectoryResultLimit() {
- return mDirectoryResultLimit;
- }
-
- public void setDirectoryResultLimit(int limit) {
- this.mDirectoryResultLimit = limit;
- }
-
- public int getContactNameDisplayOrder() {
- return mDisplayOrder;
- }
-
- public void setContactNameDisplayOrder(int displayOrder) {
- mDisplayOrder = displayOrder;
- }
-
- public int getSortOrder() {
- return mSortOrder;
- }
-
- public void setSortOrder(int sortOrder) {
- mSortOrder = sortOrder;
- }
-
- public void setPhotoLoader(ContactPhotoManager photoLoader) {
- mPhotoLoader = photoLoader;
- }
-
- protected ContactPhotoManager getPhotoLoader() {
- return mPhotoLoader;
- }
-
- public boolean getDisplayPhotos() {
- return mDisplayPhotos;
- }
-
- public void setDisplayPhotos(boolean displayPhotos) {
- mDisplayPhotos = displayPhotos;
- }
-
- public boolean isEmptyListEnabled() {
- return mEmptyListEnabled;
- }
-
- public void setEmptyListEnabled(boolean flag) {
- mEmptyListEnabled = flag;
- }
-
- public boolean isSelectionVisible() {
- return mSelectionVisible;
- }
-
- public void setSelectionVisible(boolean flag) {
- this.mSelectionVisible = flag;
- }
-
- public boolean isQuickContactEnabled() {
- return mQuickContactEnabled;
- }
-
- public void setQuickContactEnabled(boolean quickContactEnabled) {
- mQuickContactEnabled = quickContactEnabled;
- }
-
- public boolean shouldIncludeProfile() {
- return mIncludeProfile;
- }
-
- public void setIncludeProfile(boolean includeProfile) {
- 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 setDarkTheme(boolean value) {
- mDarkTheme = value;
- }
-
- public void configureDirectoryLoader(DirectoryListLoader loader) {
- loader.setDirectorySearchMode(mDirectorySearchMode);
- loader.setLocalInvisibleDirectoryEnabled(LOCAL_INVISIBLE_DIRECTORY_ENABLED);
- }
-
- /**
- * Updates partitions according to the directory meta-data contained in the supplied
- * cursor.
- */
- public void changeDirectories(Cursor cursor) {
- if (cursor.getCount() == 0) {
- // Directory table must have at least local directory, without which this adapter will
- // enter very weird state.
- Log.e(TAG, "Directory search loader returned an empty cursor, which implies we have " +
- "no directory entries.", new RuntimeException());
- return;
- }
- HashSet<Long> directoryIds = new HashSet<Long>();
-
- int idColumnIndex = cursor.getColumnIndex(Directory._ID);
- int directoryTypeColumnIndex = cursor.getColumnIndex(DirectoryListLoader.DIRECTORY_TYPE);
- int displayNameColumnIndex = cursor.getColumnIndex(Directory.DISPLAY_NAME);
- int photoSupportColumnIndex = cursor.getColumnIndex(Directory.PHOTO_SUPPORT);
-
- // TODO preserve the order of partition to match those of the cursor
- // Phase I: add new directories
- cursor.moveToPosition(-1);
- while (cursor.moveToNext()) {
- long id = cursor.getLong(idColumnIndex);
- directoryIds.add(id);
- if (getPartitionByDirectoryId(id) == -1) {
- DirectoryPartition partition = new DirectoryPartition(false, true);
- partition.setDirectoryId(id);
- partition.setDirectoryType(cursor.getString(directoryTypeColumnIndex));
- partition.setDisplayName(cursor.getString(displayNameColumnIndex));
- int photoSupport = cursor.getInt(photoSupportColumnIndex);
- partition.setPhotoSupported(photoSupport == Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY
- || photoSupport == Directory.PHOTO_SUPPORT_FULL);
- addPartition(partition);
- }
- }
-
- // Phase II: remove deleted directories
- int count = getPartitionCount();
- for (int i = count; --i >= 0; ) {
- Partition partition = getPartition(i);
- if (partition instanceof DirectoryPartition) {
- long id = ((DirectoryPartition)partition).getDirectoryId();
- if (!directoryIds.contains(id)) {
- removePartition(i);
- }
- }
- }
-
- invalidate();
- notifyDataSetChanged();
- }
-
- @Override
- public void changeCursor(int partitionIndex, Cursor cursor) {
- if (partitionIndex >= getPartitionCount()) {
- // There is no partition for this data
- return;
- }
-
- Partition partition = getPartition(partitionIndex);
- if (partition instanceof DirectoryPartition) {
- ((DirectoryPartition)partition).setStatus(DirectoryPartition.STATUS_LOADED);
- }
-
- if (mDisplayPhotos && mPhotoLoader != null && isPhotoSupported(partitionIndex)) {
- mPhotoLoader.refreshCache();
- }
-
- super.changeCursor(partitionIndex, cursor);
-
- if (isSectionHeaderDisplayEnabled() && partitionIndex == getIndexedPartition()) {
- updateIndexer(cursor);
- }
- }
-
- public void changeCursor(Cursor cursor) {
- changeCursor(0, cursor);
- }
-
- /**
- * Updates the indexer, which is used to produce section headers.
- */
- private void updateIndexer(Cursor cursor) {
- if (cursor == null) {
- setIndexer(null);
- return;
- }
-
- Bundle bundle = cursor.getExtras();
- if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES)) {
- String sections[] =
- bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
- int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
- setIndexer(new ContactsSectionIndexer(sections, counts));
- } else {
- setIndexer(null);
- }
- }
-
- @Override
- public int getViewTypeCount() {
- // We need a separate view type for each item type, plus another one for
- // each type with header, plus one for "other".
- return getItemViewTypeCount() * 2 + 1;
- }
-
- @Override
- public int getItemViewType(int partitionIndex, int position) {
- int type = super.getItemViewType(partitionIndex, position);
- if (!isUserProfile(position)
- && isSectionHeaderDisplayEnabled()
- && partitionIndex == getIndexedPartition()) {
- Placement placement = getItemPlacementInSection(position);
- return placement.firstInSection ? type : getItemViewTypeCount() + type;
- } else {
- return type;
- }
- }
-
- @Override
- public boolean isEmpty() {
- // TODO
-// if (contactsListActivity.mProviderStatus != ProviderStatus.STATUS_NORMAL) {
-// return true;
-// }
-
- if (!mEmptyListEnabled) {
- return false;
- } else if (isSearchMode()) {
- return TextUtils.isEmpty(getQueryString());
- } else if (mLoading) {
- // We don't want the empty state to show when loading.
- return false;
- } else {
- return super.isEmpty();
- }
- }
-
- public boolean isLoading() {
- int count = getPartitionCount();
- for (int i = 0; i < count; i++) {
- Partition partition = getPartition(i);
- if (partition instanceof DirectoryPartition
- && ((DirectoryPartition) partition).isLoading()) {
- return true;
- }
- }
- return false;
- }
-
- public boolean areAllPartitionsEmpty() {
- int count = getPartitionCount();
- for (int i = 0; i < count; i++) {
- if (!isPartitionEmpty(i)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Changes visibility parameters for the default directory partition.
- */
- public void configureDefaultPartition(boolean showIfEmpty, boolean hasHeader) {
- int defaultPartitionIndex = -1;
- int count = getPartitionCount();
- for (int i = 0; i < count; i++) {
- Partition partition = getPartition(i);
- if (partition instanceof DirectoryPartition &&
- ((DirectoryPartition)partition).getDirectoryId() == Directory.DEFAULT) {
- defaultPartitionIndex = i;
- break;
- }
- }
- if (defaultPartitionIndex != -1) {
- setShowIfEmpty(defaultPartitionIndex, showIfEmpty);
- setHasHeader(defaultPartitionIndex, hasHeader);
- }
- }
-
- @Override
- protected View newHeaderView(Context context, int partition, Cursor cursor,
- ViewGroup parent) {
- LayoutInflater inflater = LayoutInflater.from(context);
- return inflater.inflate(R.layout.directory_header, parent, false);
- }
-
- @Override
- protected void bindHeaderView(View view, int partitionIndex, Cursor cursor) {
- Partition partition = getPartition(partitionIndex);
- if (!(partition instanceof DirectoryPartition)) {
- return;
- }
-
- DirectoryPartition directoryPartition = (DirectoryPartition)partition;
- long directoryId = directoryPartition.getDirectoryId();
- TextView labelTextView = (TextView)view.findViewById(R.id.label);
- TextView displayNameTextView = (TextView)view.findViewById(R.id.display_name);
- if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
- labelTextView.setText(mDefaultFilterHeaderText);
- displayNameTextView.setText(null);
- } else {
- labelTextView.setText(R.string.directory_search_label);
- String directoryName = directoryPartition.getDisplayName();
- String displayName = !TextUtils.isEmpty(directoryName)
- ? directoryName
- : directoryPartition.getDirectoryType();
- displayNameTextView.setText(displayName);
- }
-
- TextView countText = (TextView)view.findViewById(R.id.count);
- if (directoryPartition.isLoading()) {
- countText.setText(R.string.search_results_searching);
- } else {
- int count = cursor == null ? 0 : cursor.getCount();
- if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE
- && count >= getDirectoryResultLimit()) {
- countText.setText(mContext.getString(
- R.string.foundTooManyContacts, getDirectoryResultLimit()));
- } else {
- countText.setText(getQuantityText(
- count, R.string.listFoundAllContactsZero, R.plurals.searchFoundContacts));
- }
- }
- }
-
- /**
- * 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) {
- return getContext().getString(zeroResourceId);
- } else {
- String format = getContext().getResources()
- .getQuantityText(pluralResourceId, count).toString();
- return String.format(format, count);
- }
- }
-
- public boolean isPhotoSupported(int partitionIndex) {
- Partition partition = getPartition(partitionIndex);
- if (partition instanceof DirectoryPartition) {
- return ((DirectoryPartition) partition).isPhotoSupported();
- }
- return true;
- }
-
- /**
- * Returns the currently selected filter.
- */
- public ContactListFilter getFilter() {
- return mFilter;
- }
-
- public void setFilter(ContactListFilter filter) {
- mFilter = filter;
- }
-
- // TODO: move sharable logic (bindXX() methods) to here with extra arguments
-
- /**
- * Loads the photo for the quick contact view and assigns the contact uri.
- * @param photoIdColumn Index of the photo id column
- * @param photoUriColumn Index of the photo uri column. Optional: Can be -1
- * @param contactIdColumn Index of the contact id column
- * @param lookUpKeyColumn Index of the lookup key column
- */
- protected void bindQuickContact(final ContactListItemView view, int partitionIndex,
- Cursor cursor, int photoIdColumn, int photoUriColumn, int contactIdColumn,
- int lookUpKeyColumn) {
- long photoId = 0;
- if (!cursor.isNull(photoIdColumn)) {
- photoId = cursor.getLong(photoIdColumn);
- }
-
- QuickContactBadge quickContact = view.getQuickContact();
- quickContact.assignContactUri(
- getContactUri(partitionIndex, cursor, contactIdColumn, lookUpKeyColumn));
-
- if (photoId != 0 || photoUriColumn == -1) {
- getPhotoLoader().loadThumbnail(quickContact, photoId, mDarkTheme);
- } else {
- final String photoUriString = cursor.getString(photoUriColumn);
- final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
- getPhotoLoader().loadPhoto(quickContact, photoUri, -1, mDarkTheme);
- }
-
- }
-
- protected Uri getContactUri(int partitionIndex, Cursor cursor,
- int contactIdColumn, int lookUpKeyColumn) {
- long contactId = cursor.getLong(contactIdColumn);
- String lookupKey = cursor.getString(lookUpKeyColumn);
- Uri uri = Contacts.getLookupUri(contactId, lookupKey);
- long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
- if (directoryId != Directory.DEFAULT) {
- uri = uri.buildUpon().appendQueryParameter(
- ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
- }
- 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 7efc57a..d522b5e 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -55,9 +55,12 @@
import com.android.common.widget.CompositeCursorAdapter.Partition;
import com.android.contacts.ContactListEmptyView;
-import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.R;
-import com.android.contacts.preference.ContactsPreferences;
+import com.android.contacts.common.ContactPhotoManager;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.DirectoryListLoader;
+import com.android.contacts.common.list.DirectoryPartition;
+import com.android.contacts.common.preference.ContactsPreferences;
import com.android.contacts.widget.ContextMenuAdapter;
/**
@@ -328,7 +331,9 @@
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == DIRECTORY_LOADER_ID) {
DirectoryListLoader loader = new DirectoryListLoader(mContext);
- mAdapter.configureDirectoryLoader(loader);
+ loader.setDirectorySearchMode(mAdapter.getDirectorySearchMode());
+ loader.setLocalInvisibleDirectoryEnabled(
+ ContactEntryListAdapter.LOCAL_INVISIBLE_DIRECTORY_ENABLED);
return loader;
} else {
CursorLoader loader = createCursorLoader();
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
deleted file mode 100644
index 1eb2dae..0000000
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * Copyright (C) 2010 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 android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.ContactCounts;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Directory;
-import android.provider.ContactsContract.SearchSnippetColumns;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-
-import com.android.contacts.R;
-
-/**
- * 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 {
-
- protected static class ContactQuery {
- private static final String[] CONTACT_PROJECTION_PRIMARY = new String[] {
- Contacts._ID, // 0
- Contacts.DISPLAY_NAME_PRIMARY, // 1
- Contacts.CONTACT_PRESENCE, // 2
- Contacts.CONTACT_STATUS, // 3
- Contacts.PHOTO_ID, // 4
- Contacts.PHOTO_THUMBNAIL_URI, // 5
- Contacts.LOOKUP_KEY, // 6
- Contacts.IS_USER_PROFILE, // 7
- };
-
- private static final String[] CONTACT_PROJECTION_ALTERNATIVE = new String[] {
- Contacts._ID, // 0
- Contacts.DISPLAY_NAME_ALTERNATIVE, // 1
- Contacts.CONTACT_PRESENCE, // 2
- Contacts.CONTACT_STATUS, // 3
- Contacts.PHOTO_ID, // 4
- Contacts.PHOTO_THUMBNAIL_URI, // 5
- Contacts.LOOKUP_KEY, // 6
- Contacts.IS_USER_PROFILE, // 7
- };
-
- private static final String[] FILTER_PROJECTION_PRIMARY = new String[] {
- Contacts._ID, // 0
- Contacts.DISPLAY_NAME_PRIMARY, // 1
- Contacts.CONTACT_PRESENCE, // 2
- Contacts.CONTACT_STATUS, // 3
- Contacts.PHOTO_ID, // 4
- Contacts.PHOTO_THUMBNAIL_URI, // 5
- Contacts.LOOKUP_KEY, // 6
- Contacts.IS_USER_PROFILE, // 7
- SearchSnippetColumns.SNIPPET, // 8
- };
-
- private static final String[] FILTER_PROJECTION_ALTERNATIVE = new String[] {
- Contacts._ID, // 0
- Contacts.DISPLAY_NAME_ALTERNATIVE, // 1
- Contacts.CONTACT_PRESENCE, // 2
- Contacts.CONTACT_STATUS, // 3
- Contacts.PHOTO_ID, // 4
- Contacts.PHOTO_THUMBNAIL_URI, // 5
- Contacts.LOOKUP_KEY, // 6
- Contacts.IS_USER_PROFILE, // 7
- SearchSnippetColumns.SNIPPET, // 8
- };
-
- public static final int CONTACT_ID = 0;
- public static final int CONTACT_DISPLAY_NAME = 1;
- public static final int CONTACT_PRESENCE_STATUS = 2;
- public static final int CONTACT_CONTACT_STATUS = 3;
- public static final int CONTACT_PHOTO_ID = 4;
- public static final int CONTACT_PHOTO_URI = 5;
- public static final int CONTACT_LOOKUP_KEY = 6;
- public static final int CONTACT_IS_USER_PROFILE = 7;
- public static final int CONTACT_SNIPPET = 8;
- }
-
- private CharSequence mUnknownNameText;
-
- private long mSelectedContactDirectoryId;
- private String mSelectedContactLookupKey;
- private long mSelectedContactId;
-
- public ContactListAdapter(Context context) {
- super(context);
-
- mUnknownNameText = context.getText(R.string.missing_name);
- }
-
- public CharSequence getUnknownNameText() {
- return mUnknownNameText;
- }
-
- public long getSelectedContactDirectoryId() {
- return mSelectedContactDirectoryId;
- }
-
- public String getSelectedContactLookupKey() {
- return mSelectedContactLookupKey;
- }
-
- public long getSelectedContactId() {
- return mSelectedContactId;
- }
-
- public void setSelectedContact(long selectedDirectoryId, String lookupKey, long contactId) {
- mSelectedContactDirectoryId = selectedDirectoryId;
- mSelectedContactLookupKey = lookupKey;
- mSelectedContactId = contactId;
- }
-
- protected static Uri buildSectionIndexerUri(Uri uri) {
- return uri.buildUpon()
- .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
- }
-
- @Override
- public String getContactDisplayName(int position) {
- return ((Cursor) getItem(position)).getString(ContactQuery.CONTACT_DISPLAY_NAME);
- }
-
- /**
- * Builds the {@link Contacts#CONTENT_LOOKUP_URI} for the given
- * {@link ListView} position.
- */
- public Uri getContactUri(int position) {
- int partitionIndex = getPartitionForPosition(position);
- Cursor item = (Cursor)getItem(position);
- return item != null ? getContactUri(partitionIndex, item) : null;
- }
-
- public Uri getContactUri(int partitionIndex, Cursor cursor) {
- long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
- String lookupKey = cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY);
- Uri uri = Contacts.getLookupUri(contactId, lookupKey);
- long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
- if (directoryId != Directory.DEFAULT) {
- uri = uri.buildUpon().appendQueryParameter(
- ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
- }
- return uri;
- }
-
- /**
- * Returns true if the specified contact is selected in the list. For a
- * contact to be shown as selected, we need both the directory and and the
- * lookup key to be the same. We are paying no attention to the contactId,
- * because it is volatile, especially in the case of directories.
- */
- public boolean isSelectedContact(int partitionIndex, Cursor cursor) {
- long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
- if (getSelectedContactDirectoryId() != directoryId) {
- return false;
- }
- String lookupKey = getSelectedContactLookupKey();
- if (lookupKey != null && TextUtils.equals(lookupKey,
- cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY))) {
- return true;
- }
-
- return directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE
- && getSelectedContactId() == cursor.getLong(ContactQuery.CONTACT_ID);
- }
-
- @Override
- protected View newView(Context context, int partition, Cursor cursor, int position,
- ViewGroup parent) {
- ContactListItemView view = new ContactListItemView(context, null);
- view.setUnknownNameText(mUnknownNameText);
- view.setQuickContactEnabled(isQuickContactEnabled());
- view.setActivatedStateSupported(isSelectionVisible());
- return view;
- }
-
- protected void bindSectionHeaderAndDivider(ContactListItemView view, int position,
- Cursor cursor) {
- if (isSectionHeaderDisplayEnabled()) {
- Placement placement = getItemPlacementInSection(position);
-
- // First position, set the contacts number string
- if (position == 0 && cursor.getInt(ContactQuery.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);
- }
- }
-
- protected void bindPhoto(final ContactListItemView view, int partitionIndex, Cursor cursor) {
- if (!isPhotoSupported(partitionIndex)) {
- view.removePhotoView();
- return;
- }
-
- // Set the photo, if available
- long photoId = 0;
- if (!cursor.isNull(ContactQuery.CONTACT_PHOTO_ID)) {
- photoId = cursor.getLong(ContactQuery.CONTACT_PHOTO_ID);
- }
-
- if (photoId != 0) {
- getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false);
- } else {
- final String photoUriString = cursor.getString(ContactQuery.CONTACT_PHOTO_URI);
- final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
- getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false);
- }
- }
-
- protected void bindName(final ContactListItemView view, Cursor cursor) {
- view.showDisplayName(
- cursor, ContactQuery.CONTACT_DISPLAY_NAME, getContactNameDisplayOrder());
- // Note: we don't show phonetic any more (See issue 5265330)
- }
-
- protected void bindPresenceAndStatusMessage(final ContactListItemView view, Cursor cursor) {
- view.showPresenceAndStatusMessage(cursor, ContactQuery.CONTACT_PRESENCE_STATUS,
- ContactQuery.CONTACT_CONTACT_STATUS);
- }
-
- protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) {
- view.showSnippet(cursor, ContactQuery.CONTACT_SNIPPET);
- }
-
- public int getSelectedContactPosition() {
- if (mSelectedContactLookupKey == null && mSelectedContactId == 0) {
- return -1;
- }
-
- Cursor cursor = null;
- int partitionIndex = -1;
- int partitionCount = getPartitionCount();
- for (int i = 0; i < partitionCount; i++) {
- DirectoryPartition partition = (DirectoryPartition) getPartition(i);
- if (partition.getDirectoryId() == mSelectedContactDirectoryId) {
- partitionIndex = i;
- break;
- }
- }
- if (partitionIndex == -1) {
- return -1;
- }
-
- cursor = getCursor(partitionIndex);
- if (cursor == null) {
- return -1;
- }
-
- cursor.moveToPosition(-1); // Reset cursor
- int offset = -1;
- while (cursor.moveToNext()) {
- if (mSelectedContactLookupKey != null) {
- String lookupKey = cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY);
- if (mSelectedContactLookupKey.equals(lookupKey)) {
- offset = cursor.getPosition();
- break;
- }
- }
- if (mSelectedContactId != 0 && (mSelectedContactDirectoryId == Directory.DEFAULT
- || mSelectedContactDirectoryId == Directory.LOCAL_INVISIBLE)) {
- long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
- if (contactId == mSelectedContactId) {
- offset = cursor.getPosition();
- break;
- }
- }
- }
- if (offset == -1) {
- return -1;
- }
-
- int position = getPositionForPartition(partitionIndex) + offset;
- if (hasHeader(partitionIndex)) {
- position++;
- }
- return position;
- }
-
- public boolean hasValidSelection() {
- return getSelectedContactPosition() != -1;
- }
-
- public Uri getFirstContactUri() {
- int partitionCount = getPartitionCount();
- for (int i = 0; i < partitionCount; i++) {
- DirectoryPartition partition = (DirectoryPartition) getPartition(i);
- if (partition.isLoading()) {
- continue;
- }
-
- Cursor cursor = getCursor(i);
- if (cursor == null) {
- continue;
- }
-
- if (!cursor.moveToFirst()) {
- continue;
- }
-
- return getContactUri(i, cursor);
- }
-
- 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(ContactQuery.CONTACT_IS_USER_PROFILE) == 1);
- }
- }
-
- /**
- * @return Projection useful for children.
- */
- protected final String[] getProjection(boolean forSearch) {
- final int sortOrder = getContactNameDisplayOrder();
- if (forSearch) {
- if (sortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
- return ContactQuery.FILTER_PROJECTION_PRIMARY;
- } else {
- return ContactQuery.FILTER_PROJECTION_ALTERNATIVE;
- }
- } else {
- if (sortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
- return ContactQuery.CONTACT_PROJECTION_PRIMARY;
- } else {
- return ContactQuery.CONTACT_PROJECTION_ALTERNATIVE;
- }
- }
- }
-}
diff --git a/src/com/android/contacts/list/ContactListFilter.java b/src/com/android/contacts/list/ContactListFilter.java
deleted file mode 100644
index 172cbe2..0000000
--- a/src/com/android/contacts/list/ContactListFilter.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (C) 2010 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 android.content.SharedPreferences;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.provider.ContactsContract.RawContacts;
-import android.text.TextUtils;
-
-/**
- * Contact list filter parameters.
- */
-public final class ContactListFilter implements Comparable<ContactListFilter>, Parcelable {
-
- public static final int FILTER_TYPE_DEFAULT = -1;
- public static final int FILTER_TYPE_ALL_ACCOUNTS = -2;
- public static final int FILTER_TYPE_CUSTOM = -3;
- public static final int FILTER_TYPE_STARRED = -4;
- public static final int FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY = -5;
- public static final int FILTER_TYPE_SINGLE_CONTACT = -6;
-
- public static final int FILTER_TYPE_ACCOUNT = 0;
-
- /**
- * Obsolete filter which had been used in Honeycomb. This may be stored in
- * {@link SharedPreferences}, but should be replaced with ALL filter when it is found.
- *
- * TODO: "group" filter and relevant variables are all obsolete. Remove them.
- */
- private static final int FILTER_TYPE_GROUP = 1;
-
- private static final String KEY_FILTER_TYPE = "filter.type";
- private static final String KEY_ACCOUNT_NAME = "filter.accountName";
- private static final String KEY_ACCOUNT_TYPE = "filter.accountType";
- private static final String KEY_DATA_SET = "filter.dataSet";
-
- public final int filterType;
- public final String accountType;
- public final String accountName;
- public final String dataSet;
- public final Drawable icon;
- private String mId;
-
- public ContactListFilter(int filterType, String accountType, String accountName, String dataSet,
- Drawable icon) {
- this.filterType = filterType;
- this.accountType = accountType;
- this.accountName = accountName;
- this.dataSet = dataSet;
- this.icon = icon;
- }
-
- public static ContactListFilter createFilterWithType(int filterType) {
- return new ContactListFilter(filterType, null, null, null, null);
- }
-
- public static ContactListFilter createAccountFilter(String accountType, String accountName,
- String dataSet, Drawable icon) {
- return new ContactListFilter(ContactListFilter.FILTER_TYPE_ACCOUNT, accountType,
- accountName, dataSet, icon);
- }
-
- /**
- * Returns true if this filter is based on data and may become invalid over time.
- */
- public boolean isValidationRequired() {
- return filterType == FILTER_TYPE_ACCOUNT;
- }
-
- @Override
- public String toString() {
- switch (filterType) {
- case FILTER_TYPE_DEFAULT:
- return "default";
- case FILTER_TYPE_ALL_ACCOUNTS:
- return "all_accounts";
- case FILTER_TYPE_CUSTOM:
- return "custom";
- case FILTER_TYPE_STARRED:
- return "starred";
- case FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY:
- return "with_phones";
- case FILTER_TYPE_SINGLE_CONTACT:
- return "single";
- case FILTER_TYPE_ACCOUNT:
- return "account: " + accountType + (dataSet != null ? "/" + dataSet : "")
- + " " + accountName;
- }
- return super.toString();
- }
-
- @Override
- public int compareTo(ContactListFilter another) {
- int res = accountName.compareTo(another.accountName);
- if (res != 0) {
- return res;
- }
-
- res = accountType.compareTo(another.accountType);
- if (res != 0) {
- return res;
- }
-
- return filterType - another.filterType;
- }
-
- @Override
- public int hashCode() {
- int code = filterType;
- if (accountType != null) {
- code = code * 31 + accountType.hashCode();
- code = code * 31 + accountName.hashCode();
- }
- if (dataSet != null) {
- code = code * 31 + dataSet.hashCode();
- }
- return code;
- }
-
- @Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
-
- if (!(other instanceof ContactListFilter)) {
- return false;
- }
-
- ContactListFilter otherFilter = (ContactListFilter) other;
- if (filterType != otherFilter.filterType
- || !TextUtils.equals(accountName, otherFilter.accountName)
- || !TextUtils.equals(accountType, otherFilter.accountType)
- || !TextUtils.equals(dataSet, otherFilter.dataSet)) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Store the given {@link ContactListFilter} to preferences. If the requested filter is
- * of type {@link #FILTER_TYPE_SINGLE_CONTACT} then do not save it to preferences because
- * it is a temporary state.
- */
- public static void storeToPreferences(SharedPreferences prefs, ContactListFilter filter) {
- if (filter != null && filter.filterType == FILTER_TYPE_SINGLE_CONTACT) {
- return;
- }
- prefs.edit()
- .putInt(KEY_FILTER_TYPE, filter == null ? FILTER_TYPE_DEFAULT : filter.filterType)
- .putString(KEY_ACCOUNT_NAME, filter == null ? null : filter.accountName)
- .putString(KEY_ACCOUNT_TYPE, filter == null ? null : filter.accountType)
- .putString(KEY_DATA_SET, filter == null ? null : filter.dataSet)
- .apply();
- }
-
- /**
- * Try to obtain ContactListFilter object saved in SharedPreference.
- * If there's no info there, return ALL filter instead.
- */
- public static ContactListFilter restoreDefaultPreferences(SharedPreferences prefs) {
- ContactListFilter filter = restoreFromPreferences(prefs);
- if (filter == null) {
- filter = ContactListFilter.createFilterWithType(FILTER_TYPE_ALL_ACCOUNTS);
- }
- // "Group" filter is obsolete and thus is not exposed anymore. The "single contact mode"
- // should also not be stored in preferences anymore since it is a temporary state.
- if (filter.filterType == FILTER_TYPE_GROUP ||
- filter.filterType == FILTER_TYPE_SINGLE_CONTACT) {
- filter = ContactListFilter.createFilterWithType(FILTER_TYPE_ALL_ACCOUNTS);
- }
- return filter;
- }
-
- private static ContactListFilter restoreFromPreferences(SharedPreferences prefs) {
- int filterType = prefs.getInt(KEY_FILTER_TYPE, FILTER_TYPE_DEFAULT);
- if (filterType == FILTER_TYPE_DEFAULT) {
- return null;
- }
-
- String accountName = prefs.getString(KEY_ACCOUNT_NAME, null);
- String accountType = prefs.getString(KEY_ACCOUNT_TYPE, null);
- String dataSet = prefs.getString(KEY_DATA_SET, null);
- return new ContactListFilter(filterType, accountType, accountName, dataSet, null);
- }
-
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(filterType);
- dest.writeString(accountName);
- dest.writeString(accountType);
- dest.writeString(dataSet);
- }
-
- public static final Parcelable.Creator<ContactListFilter> CREATOR =
- new Parcelable.Creator<ContactListFilter>() {
- @Override
- public ContactListFilter createFromParcel(Parcel source) {
- int filterType = source.readInt();
- String accountName = source.readString();
- String accountType = source.readString();
- String dataSet = source.readString();
- return new ContactListFilter(filterType, accountType, accountName, dataSet, null);
- }
-
- @Override
- public ContactListFilter[] newArray(int size) {
- return new ContactListFilter[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- /**
- * Returns a string that can be used as a stable persistent identifier for this filter.
- */
- public String getId() {
- if (mId == null) {
- StringBuilder sb = new StringBuilder();
- sb.append(filterType);
- if (accountType != null) {
- sb.append('-').append(accountType);
- }
- if (dataSet != null) {
- sb.append('/').append(dataSet);
- }
- if (accountName != null) {
- sb.append('-').append(accountName.replace('-', '_'));
- }
- mId = sb.toString();
- }
- return mId;
- }
-
- /**
- * Adds the account query parameters to the given {@code uriBuilder}.
- *
- * @throws IllegalStateException if the filter type is not {@link #FILTER_TYPE_ACCOUNT}.
- */
- public Uri.Builder addAccountQueryParameterToUrl(Uri.Builder uriBuilder) {
- if (filterType != FILTER_TYPE_ACCOUNT) {
- throw new IllegalStateException("filterType must be FILTER_TYPE_ACCOUNT");
- }
- uriBuilder.appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName);
- uriBuilder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType);
- if (!TextUtils.isEmpty(dataSet)) {
- uriBuilder.appendQueryParameter(RawContacts.DATA_SET, dataSet);
- }
- return uriBuilder;
- }
-
- public String toDebugString() {
- final StringBuilder builder = new StringBuilder();
- builder.append("[filter type: " + filterType + " (" + filterTypeToString(filterType) + ")");
- if (filterType == FILTER_TYPE_ACCOUNT) {
- builder.append(", accountType: " + accountType)
- .append(", accountName: " + accountName)
- .append(", dataSet: " + dataSet);
- }
- builder.append(", icon: " + icon + "]");
- return builder.toString();
- }
-
- public static final String filterTypeToString(int filterType) {
- switch (filterType) {
- case FILTER_TYPE_DEFAULT:
- return "FILTER_TYPE_DEFAULT";
- case FILTER_TYPE_ALL_ACCOUNTS:
- return "FILTER_TYPE_ALL_ACCOUNTS";
- case FILTER_TYPE_CUSTOM:
- return "FILTER_TYPE_CUSTOM";
- case FILTER_TYPE_STARRED:
- return "FILTER_TYPE_STARRED";
- case FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY:
- return "FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY";
- case FILTER_TYPE_SINGLE_CONTACT:
- return "FILTER_TYPE_SINGLE_CONTACT";
- case FILTER_TYPE_ACCOUNT:
- return "FILTER_TYPE_ACCOUNT";
- default:
- return "(unknown)";
- }
- }
-}
diff --git a/src/com/android/contacts/list/ContactListFilterController.java b/src/com/android/contacts/list/ContactListFilterController.java
index 5377df2..b8086b0 100644
--- a/src/com/android/contacts/list/ContactListFilterController.java
+++ b/src/com/android/contacts/list/ContactListFilterController.java
@@ -19,6 +19,7 @@
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
+import com.android.contacts.common.list.ContactListFilter;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.account.AccountWithDataSet;
@@ -26,7 +27,7 @@
import java.util.List;
/**
- * Manages {@link ContactListFilter}. All methods must be called from UI thread.
+ * Manages {@link com.android.contacts.common.list.ContactListFilter}. All methods must be called from UI thread.
*/
public abstract class ContactListFilterController {
diff --git a/src/com/android/contacts/list/ContactListFilterView.java b/src/com/android/contacts/list/ContactListFilterView.java
index d0ecfe4..a71809b 100644
--- a/src/com/android/contacts/list/ContactListFilterView.java
+++ b/src/com/android/contacts/list/ContactListFilterView.java
@@ -26,6 +26,7 @@
import android.widget.TextView;
import com.android.contacts.R;
+import com.android.contacts.common.list.ContactListFilter;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.account.AccountType;
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
deleted file mode 100644
index bef1c44..0000000
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ /dev/null
@@ -1,1215 +0,0 @@
-/*
- * Copyright (C) 2010 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 android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.database.CharArrayBuffer;
-import android.database.Cursor;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.TextUtils.TruncateAt;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView.SelectionBoundsAdjuster;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.QuickContactBadge;
-import android.widget.TextView;
-
-import com.android.contacts.common.ContactPresenceIconUtil;
-import com.android.contacts.common.ContactStatusUtil;
-import com.android.contacts.R;
-import com.android.contacts.common.format.PrefixHighlighter;
-
-/**
- * A custom view for an item in the contact list.
- * The view contains the contact's photo, a set of text views (for name, status, etc...) and
- * icons for presence and call.
- * The view uses no XML file for layout and all the measurements and layouts are done
- * in the onMeasure and onLayout methods.
- *
- * The layout puts the contact's photo on the right side of the view, the call icon (if present)
- * to the left of the photo, the text lines are aligned to the left and the presence icon (if
- * present) is set to the left of the status line.
- *
- * The layout also supports a header (used as a header of a group of contacts) that is above the
- * contact's data and a divider between contact view.
- */
-
-public class ContactListItemView extends ViewGroup
- implements SelectionBoundsAdjuster {
-
- // Style values for layout and appearance
- private final int mPreferredHeight;
- private final int mGapBetweenImageAndText;
- private final int mGapBetweenLabelAndData;
- private final int mPresenceIconMargin;
- private final int mPresenceIconSize;
- private final int mHeaderTextColor;
- private final int mHeaderTextIndent;
- private final int mHeaderTextSize;
- private final int mHeaderUnderlineHeight;
- private final int mHeaderUnderlineColor;
- private final int mCountViewTextSize;
- private final int mContactsCountTextColor;
- private final int mTextIndent;
- private Drawable mActivatedBackgroundDrawable;
-
- /**
- * Used with {@link #mLabelView}, specifying the width ratio between label and data.
- */
- private final int mLabelViewWidthWeight;
- /**
- * Used with {@link #mDataView}, specifying the width ratio between label and data.
- */
- private final int mDataViewWidthWeight;
-
- // Will be used with adjustListItemSelectionBounds().
- private int mSelectionBoundsMarginLeft;
- private int mSelectionBoundsMarginRight;
-
- // Horizontal divider between contact views.
- private boolean mHorizontalDividerVisible = true;
- private Drawable mHorizontalDividerDrawable;
- private int mHorizontalDividerHeight;
-
- /**
- * Where to put contact photo. This affects the other Views' layout or look-and-feel.
- */
- public enum PhotoPosition {
- LEFT,
- RIGHT
- }
- public static final PhotoPosition DEFAULT_PHOTO_POSITION = PhotoPosition.RIGHT;
- private PhotoPosition mPhotoPosition = DEFAULT_PHOTO_POSITION;
-
- // Header layout data
- private boolean mHeaderVisible;
- private View mHeaderDivider;
- private int mHeaderBackgroundHeight;
- private TextView mHeaderTextView;
-
- // The views inside the contact view
- private boolean mQuickContactEnabled = true;
- private QuickContactBadge mQuickContact;
- private ImageView mPhotoView;
- private TextView mNameTextView;
- private TextView mPhoneticNameTextView;
- private TextView mLabelView;
- private TextView mDataView;
- private TextView mSnippetView;
- private TextView mStatusView;
- private TextView mCountView;
- private ImageView mPresenceIcon;
-
- private ColorStateList mSecondaryTextColor;
-
- private char[] mHighlightedPrefix;
-
- private int mDefaultPhotoViewSize;
- /**
- * Can be effective even when {@link #mPhotoView} is null, as we want to have horizontal padding
- * to align other data in this View.
- */
- private int mPhotoViewWidth;
- /**
- * Can be effective even when {@link #mPhotoView} is null, as we want to have vertical padding.
- */
- private int mPhotoViewHeight;
-
- /**
- * Only effective when {@link #mPhotoView} is null.
- * When true all the Views on the right side of the photo should have horizontal padding on
- * those left assuming there is a photo.
- */
- private boolean mKeepHorizontalPaddingForPhotoView;
- /**
- * Only effective when {@link #mPhotoView} is null.
- */
- private boolean mKeepVerticalPaddingForPhotoView;
-
- /**
- * True when {@link #mPhotoViewWidth} and {@link #mPhotoViewHeight} are ready for being used.
- * False indicates those values should be updated before being used in position calculation.
- */
- private boolean mPhotoViewWidthAndHeightAreReady = false;
-
- private int mNameTextViewHeight;
- private int mPhoneticNameTextViewHeight;
- private int mLabelViewHeight;
- private int mDataViewHeight;
- private int mSnippetTextViewHeight;
- private int mStatusTextViewHeight;
-
- // Holds Math.max(mLabelTextViewHeight, mDataViewHeight), assuming Label and Data share the
- // same row.
- private int mLabelAndDataViewMaxHeight;
-
- // TODO: some TextView fields are using CharArrayBuffer while some are not. Determine which is
- // more efficient for each case or in general, and simplify the whole implementation.
- // Note: if we're sure MARQUEE will be used every time, there's no reason to use
- // CharArrayBuffer, since MARQUEE requires Span and thus we need to copy characters inside the
- // buffer to Spannable once, while CharArrayBuffer is for directly applying char array to
- // TextView without any modification.
- private final CharArrayBuffer mDataBuffer = new CharArrayBuffer(128);
- private final CharArrayBuffer mPhoneticNameBuffer = new CharArrayBuffer(128);
-
- private boolean mActivatedStateSupported;
-
- private Rect mBoundsWithoutHeader = new Rect();
-
- /** A helper used to highlight a prefix in a text field. */
- private PrefixHighlighter mPrefixHighlighter;
- private CharSequence mUnknownNameText;
-
- public ContactListItemView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
-
- // Read all style values
- TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ContactListItemView);
- mPreferredHeight = a.getDimensionPixelSize(
- R.styleable.ContactListItemView_list_item_height, 0);
- mActivatedBackgroundDrawable = a.getDrawable(
- R.styleable.ContactListItemView_activated_background);
- mHorizontalDividerDrawable = a.getDrawable(
- R.styleable.ContactListItemView_list_item_divider);
-
- mGapBetweenImageAndText = a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_gap_between_image_and_text, 0);
- mGapBetweenLabelAndData = a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_gap_between_label_and_data, 0);
- mPresenceIconMargin = a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_presence_icon_margin, 4);
- mPresenceIconSize = a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_presence_icon_size, 16);
- mDefaultPhotoViewSize = a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_photo_size, 0);
- mHeaderTextIndent = a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_header_text_indent, 0);
- mHeaderTextColor = a.getColor(
- R.styleable.ContactListItemView_list_item_header_text_color, Color.BLACK);
- mHeaderTextSize = a.getDimensionPixelSize(
- R.styleable.ContactListItemView_list_item_header_text_size, 12);
- mHeaderBackgroundHeight = a.getDimensionPixelSize(
- R.styleable.ContactListItemView_list_item_header_height, 30);
- mHeaderUnderlineHeight = a.getDimensionPixelSize(
- R.styleable.ContactListItemView_list_item_header_underline_height, 1);
- mHeaderUnderlineColor = a.getColor(
- R.styleable.ContactListItemView_list_item_header_underline_color, 0);
- mTextIndent = a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_text_indent, 0);
- mCountViewTextSize = a.getDimensionPixelSize(
- R.styleable.ContactListItemView_list_item_contacts_count_text_size, 12);
- mContactsCountTextColor = a.getColor(
- R.styleable.ContactListItemView_list_item_contacts_count_text_color, Color.BLACK);
- mDataViewWidthWeight = a.getInteger(
- R.styleable.ContactListItemView_list_item_data_width_weight, 5);
- mLabelViewWidthWeight = a.getInteger(
- R.styleable.ContactListItemView_list_item_label_width_weight, 3);
-
- setPadding(
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_padding_left, 0),
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_padding_top, 0),
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_padding_right, 0),
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_padding_bottom, 0));
-
- final int prefixHighlightColor = a.getColor(
- R.styleable.ContactListItemView_list_item_prefix_highlight_color, Color.GREEN);
- mPrefixHighlighter = new PrefixHighlighter(prefixHighlightColor);
- a.recycle();
-
- a = getContext().obtainStyledAttributes(android.R.styleable.Theme);
- mSecondaryTextColor = a.getColorStateList(android.R.styleable.Theme_textColorSecondary);
- a.recycle();
-
- mHorizontalDividerHeight = mHorizontalDividerDrawable.getIntrinsicHeight();
-
- if (mActivatedBackgroundDrawable != null) {
- mActivatedBackgroundDrawable.setCallback(this);
- }
- }
-
- public void setUnknownNameText(CharSequence unknownNameText) {
- mUnknownNameText = unknownNameText;
- }
-
- public void setQuickContactEnabled(boolean flag) {
- mQuickContactEnabled = flag;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // We will match parent's width and wrap content vertically, but make sure
- // height is no less than listPreferredItemHeight.
- final int specWidth = resolveSize(0, widthMeasureSpec);
- final int preferredHeight;
- if (mHorizontalDividerVisible) {
- preferredHeight = mPreferredHeight + mHorizontalDividerHeight;
- } else {
- preferredHeight = mPreferredHeight;
- }
-
- mNameTextViewHeight = 0;
- mPhoneticNameTextViewHeight = 0;
- mLabelViewHeight = 0;
- mDataViewHeight = 0;
- mLabelAndDataViewMaxHeight = 0;
- mSnippetTextViewHeight = 0;
- mStatusTextViewHeight = 0;
-
- ensurePhotoViewSize();
-
- // Width each TextView is able to use.
- final int effectiveWidth;
- // All the other Views will honor the photo, so available width for them may be shrunk.
- if (mPhotoViewWidth > 0 || mKeepHorizontalPaddingForPhotoView) {
- effectiveWidth = specWidth - getPaddingLeft() - getPaddingRight()
- - (mPhotoViewWidth + mGapBetweenImageAndText);
- } else {
- effectiveWidth = specWidth - getPaddingLeft() - getPaddingRight();
- }
-
- // Go over all visible text views and measure actual width of each of them.
- // Also calculate their heights to get the total height for this entire view.
-
- if (isVisible(mNameTextView)) {
- // Caculate width for name text - this parallels similar measurement in onLayout.
- int nameTextWidth = effectiveWidth;
- if (mPhotoPosition != PhotoPosition.LEFT) {
- nameTextWidth -= mTextIndent;
- }
- mNameTextView.measure(
- MeasureSpec.makeMeasureSpec(nameTextWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- mNameTextViewHeight = mNameTextView.getMeasuredHeight();
- }
-
- if (isVisible(mPhoneticNameTextView)) {
- mPhoneticNameTextView.measure(
- MeasureSpec.makeMeasureSpec(effectiveWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- mPhoneticNameTextViewHeight = mPhoneticNameTextView.getMeasuredHeight();
- }
-
- // If both data (phone number/email address) and label (type like "MOBILE") are quite long,
- // we should ellipsize both using appropriate ratio.
- final int dataWidth;
- final int labelWidth;
- if (isVisible(mDataView)) {
- if (isVisible(mLabelView)) {
- final int totalWidth = effectiveWidth - mGapBetweenLabelAndData;
- dataWidth = ((totalWidth * mDataViewWidthWeight)
- / (mDataViewWidthWeight + mLabelViewWidthWeight));
- labelWidth = ((totalWidth * mLabelViewWidthWeight) /
- (mDataViewWidthWeight + mLabelViewWidthWeight));
- } else {
- dataWidth = effectiveWidth;
- labelWidth = 0;
- }
- } else {
- dataWidth = 0;
- if (isVisible(mLabelView)) {
- labelWidth = effectiveWidth;
- } else {
- labelWidth = 0;
- }
- }
-
- if (isVisible(mDataView)) {
- mDataView.measure(MeasureSpec.makeMeasureSpec(dataWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- mDataViewHeight = mDataView.getMeasuredHeight();
- }
-
- if (isVisible(mLabelView)) {
- // For performance reason we don't want AT_MOST usually, but when the picture is
- // on right, we need to use it anyway because mDataView is next to mLabelView.
- final int mode = (mPhotoPosition == PhotoPosition.LEFT
- ? MeasureSpec.EXACTLY : MeasureSpec.AT_MOST);
- mLabelView.measure(MeasureSpec.makeMeasureSpec(labelWidth, mode),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- mLabelViewHeight = mLabelView.getMeasuredHeight();
- }
- mLabelAndDataViewMaxHeight = Math.max(mLabelViewHeight, mDataViewHeight);
-
- if (isVisible(mSnippetView)) {
- mSnippetView.measure(
- MeasureSpec.makeMeasureSpec(effectiveWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- mSnippetTextViewHeight = mSnippetView.getMeasuredHeight();
- }
-
- // Status view height is the biggest of the text view and the presence icon
- if (isVisible(mPresenceIcon)) {
- mPresenceIcon.measure(
- MeasureSpec.makeMeasureSpec(mPresenceIconSize, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(mPresenceIconSize, MeasureSpec.EXACTLY));
- mStatusTextViewHeight = mPresenceIcon.getMeasuredHeight();
- }
-
- if (isVisible(mStatusView)) {
- // Presence and status are in a same row, so status will be affected by icon size.
- final int statusWidth;
- if (isVisible(mPresenceIcon)) {
- statusWidth = (effectiveWidth - mPresenceIcon.getMeasuredWidth()
- - mPresenceIconMargin);
- } else {
- statusWidth = effectiveWidth;
- }
- mStatusView.measure(MeasureSpec.makeMeasureSpec(statusWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- mStatusTextViewHeight =
- Math.max(mStatusTextViewHeight, mStatusView.getMeasuredHeight());
- }
-
- // Calculate height including padding.
- int height = (mNameTextViewHeight + mPhoneticNameTextViewHeight +
- mLabelAndDataViewMaxHeight +
- mSnippetTextViewHeight + mStatusTextViewHeight);
-
- // Make sure the height is at least as high as the photo
- height = Math.max(height, mPhotoViewHeight + getPaddingBottom() + getPaddingTop());
-
- // Add horizontal divider height
- if (mHorizontalDividerVisible) {
- height += mHorizontalDividerHeight;
- }
-
- // Make sure height is at least the preferred height
- height = Math.max(height, preferredHeight);
-
- // Add the height of the header if visible
- if (mHeaderVisible) {
- mHeaderTextView.measure(
- MeasureSpec.makeMeasureSpec(specWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
- if (mCountView != null) {
- mCountView.measure(
- MeasureSpec.makeMeasureSpec(specWidth, MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
- }
- mHeaderBackgroundHeight = Math.max(mHeaderBackgroundHeight,
- mHeaderTextView.getMeasuredHeight());
- height += (mHeaderBackgroundHeight + mHeaderUnderlineHeight);
- }
-
- setMeasuredDimension(specWidth, height);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- final int height = bottom - top;
- final int width = right - left;
-
- // Determine the vertical bounds by laying out the header first.
- int topBound = 0;
- int bottomBound = height;
- int leftBound = getPaddingLeft();
- int rightBound = width - getPaddingRight();
-
- // Put the header in the top of the contact view (Text + underline view)
- if (mHeaderVisible) {
- mHeaderTextView.layout(leftBound + mHeaderTextIndent,
- 0,
- rightBound,
- mHeaderBackgroundHeight);
- if (mCountView != null) {
- mCountView.layout(rightBound - mCountView.getMeasuredWidth(),
- 0,
- rightBound,
- mHeaderBackgroundHeight);
- }
- mHeaderDivider.layout(leftBound,
- mHeaderBackgroundHeight,
- rightBound,
- mHeaderBackgroundHeight + mHeaderUnderlineHeight);
- topBound += (mHeaderBackgroundHeight + mHeaderUnderlineHeight);
- }
-
- // Put horizontal divider at the bottom
- if (mHorizontalDividerVisible) {
- mHorizontalDividerDrawable.setBounds(
- leftBound,
- height - mHorizontalDividerHeight,
- rightBound,
- height);
- bottomBound -= mHorizontalDividerHeight;
- }
-
- mBoundsWithoutHeader.set(0, topBound, width, bottomBound);
-
- if (mActivatedStateSupported && isActivated()) {
- mActivatedBackgroundDrawable.setBounds(mBoundsWithoutHeader);
- }
-
- final View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
- if (mPhotoPosition == PhotoPosition.LEFT) {
- // Photo is the left most view. All the other Views should on the right of the photo.
- if (photoView != null) {
- // Center the photo vertically
- final int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
- photoView.layout(
- leftBound,
- photoTop,
- leftBound + mPhotoViewWidth,
- photoTop + mPhotoViewHeight);
- leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
- } else if (mKeepHorizontalPaddingForPhotoView) {
- // Draw nothing but keep the padding.
- leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
- }
- } else {
- // Photo is the right most view. Right bound should be adjusted that way.
- if (photoView != null) {
- // Center the photo vertically
- final int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
- photoView.layout(
- rightBound - mPhotoViewWidth,
- photoTop,
- rightBound,
- photoTop + mPhotoViewHeight);
- rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
- }
-
- // Add indent between left-most padding and texts.
- leftBound += mTextIndent;
- }
-
- // Center text vertically
- final int totalTextHeight = mNameTextViewHeight + mPhoneticNameTextViewHeight +
- mLabelAndDataViewMaxHeight + mSnippetTextViewHeight + mStatusTextViewHeight;
- int textTopBound = (bottomBound + topBound - totalTextHeight) / 2;
-
- // Layout all text view and presence icon
- // Put name TextView first
- if (isVisible(mNameTextView)) {
- mNameTextView.layout(leftBound,
- textTopBound,
- rightBound,
- textTopBound + mNameTextViewHeight);
- textTopBound += mNameTextViewHeight;
- }
-
- // Presence and status
- int statusLeftBound = leftBound;
- if (isVisible(mPresenceIcon)) {
- int iconWidth = mPresenceIcon.getMeasuredWidth();
- mPresenceIcon.layout(
- leftBound,
- textTopBound,
- leftBound + iconWidth,
- textTopBound + mStatusTextViewHeight);
- statusLeftBound += (iconWidth + mPresenceIconMargin);
- }
-
- if (isVisible(mStatusView)) {
- mStatusView.layout(statusLeftBound,
- textTopBound,
- rightBound,
- textTopBound + mStatusTextViewHeight);
- }
-
- if (isVisible(mStatusView) || isVisible(mPresenceIcon)) {
- textTopBound += mStatusTextViewHeight;
- }
-
- // Rest of text views
- int dataLeftBound = leftBound;
- if (isVisible(mPhoneticNameTextView)) {
- mPhoneticNameTextView.layout(leftBound,
- textTopBound,
- rightBound,
- textTopBound + mPhoneticNameTextViewHeight);
- textTopBound += mPhoneticNameTextViewHeight;
- }
-
- // Label and Data align bottom.
- if (isVisible(mLabelView)) {
- if (mPhotoPosition == PhotoPosition.LEFT) {
- // When photo is on left, label is placed on the right edge of the list item.
- mLabelView.layout(rightBound - mLabelView.getMeasuredWidth(),
- textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
- rightBound,
- textTopBound + mLabelAndDataViewMaxHeight);
- rightBound -= mLabelView.getMeasuredWidth();
- } else {
- // When photo is on right, label is placed on the left of data view.
- dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
- mLabelView.layout(leftBound,
- textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
- dataLeftBound,
- textTopBound + mLabelAndDataViewMaxHeight);
- dataLeftBound += mGapBetweenLabelAndData;
- }
- }
-
- if (isVisible(mDataView)) {
- mDataView.layout(dataLeftBound,
- textTopBound + mLabelAndDataViewMaxHeight - mDataViewHeight,
- rightBound,
- textTopBound + mLabelAndDataViewMaxHeight);
- }
- if (isVisible(mLabelView) || isVisible(mDataView)) {
- textTopBound += mLabelAndDataViewMaxHeight;
- }
-
- if (isVisible(mSnippetView)) {
- mSnippetView.layout(leftBound,
- textTopBound,
- rightBound,
- textTopBound + mSnippetTextViewHeight);
- }
- }
-
- @Override
- public void adjustListItemSelectionBounds(Rect bounds) {
- bounds.top += mBoundsWithoutHeader.top;
- bounds.bottom = bounds.top + mBoundsWithoutHeader.height();
- bounds.left += mSelectionBoundsMarginLeft;
- bounds.right -= mSelectionBoundsMarginRight;
- }
-
- protected boolean isVisible(View view) {
- return view != null && view.getVisibility() == View.VISIBLE;
- }
-
- /**
- * Extracts width and height from the style
- */
- private void ensurePhotoViewSize() {
- if (!mPhotoViewWidthAndHeightAreReady) {
- mPhotoViewWidth = mPhotoViewHeight = getDefaultPhotoViewSize();
- if (!mQuickContactEnabled && mPhotoView == null) {
- if (!mKeepHorizontalPaddingForPhotoView) {
- mPhotoViewWidth = 0;
- }
- if (!mKeepVerticalPaddingForPhotoView) {
- mPhotoViewHeight = 0;
- }
- }
-
- mPhotoViewWidthAndHeightAreReady = true;
- }
- }
-
- protected void setDefaultPhotoViewSize(int pixels) {
- mDefaultPhotoViewSize = pixels;
- }
-
- protected int getDefaultPhotoViewSize() {
- return mDefaultPhotoViewSize;
- }
-
- /**
- * Gets a LayoutParam that corresponds to the default photo size.
- *
- * @return A new LayoutParam.
- */
- private LayoutParams getDefaultPhotoLayoutParams() {
- LayoutParams params = generateDefaultLayoutParams();
- params.width = getDefaultPhotoViewSize();
- params.height = params.width;
- return params;
- }
-
- @Override
- protected void drawableStateChanged() {
- super.drawableStateChanged();
- if (mActivatedStateSupported) {
- mActivatedBackgroundDrawable.setState(getDrawableState());
- }
- }
-
- @Override
- protected boolean verifyDrawable(Drawable who) {
- return who == mActivatedBackgroundDrawable || super.verifyDrawable(who);
- }
-
- @Override
- public void jumpDrawablesToCurrentState() {
- super.jumpDrawablesToCurrentState();
- if (mActivatedStateSupported) {
- mActivatedBackgroundDrawable.jumpToCurrentState();
- }
- }
-
- @Override
- public void dispatchDraw(Canvas canvas) {
- if (mActivatedStateSupported && isActivated()) {
- mActivatedBackgroundDrawable.draw(canvas);
- }
- if (mHorizontalDividerVisible) {
- mHorizontalDividerDrawable.draw(canvas);
- }
-
- super.dispatchDraw(canvas);
- }
-
- /**
- * Sets the flag that determines whether a divider should drawn at the bottom
- * of the view.
- */
- public void setDividerVisible(boolean visible) {
- mHorizontalDividerVisible = visible;
- }
-
- /**
- * Sets section header or makes it invisible if the title is null.
- */
- public void setSectionHeader(String title) {
- if (!TextUtils.isEmpty(title)) {
- if (mHeaderTextView == null) {
- mHeaderTextView = new TextView(mContext);
- mHeaderTextView.setTextColor(mHeaderTextColor);
- mHeaderTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mHeaderTextSize);
- mHeaderTextView.setTypeface(mHeaderTextView.getTypeface(), Typeface.BOLD);
- mHeaderTextView.setGravity(Gravity.CENTER_VERTICAL);
- addView(mHeaderTextView);
- }
- if (mHeaderDivider == null) {
- mHeaderDivider = new View(mContext);
- mHeaderDivider.setBackgroundColor(mHeaderUnderlineColor);
- addView(mHeaderDivider);
- }
- setMarqueeText(mHeaderTextView, title);
- mHeaderTextView.setVisibility(View.VISIBLE);
- mHeaderDivider.setVisibility(View.VISIBLE);
- mHeaderTextView.setAllCaps(true);
- mHeaderVisible = true;
- } else {
- if (mHeaderTextView != null) {
- mHeaderTextView.setVisibility(View.GONE);
- }
- if (mHeaderDivider != null) {
- mHeaderDivider.setVisibility(View.GONE);
- }
- mHeaderVisible = false;
- }
- }
-
- /**
- * Returns the quick contact badge, creating it if necessary.
- */
- public QuickContactBadge getQuickContact() {
- if (!mQuickContactEnabled) {
- throw new IllegalStateException("QuickContact is disabled for this view");
- }
- if (mQuickContact == null) {
- mQuickContact = new QuickContactBadge(mContext);
- mQuickContact.setLayoutParams(getDefaultPhotoLayoutParams());
- if (mNameTextView != null) {
- mQuickContact.setContentDescription(mContext.getString(
- R.string.description_quick_contact_for, mNameTextView.getText()));
- }
-
- addView(mQuickContact);
- mPhotoViewWidthAndHeightAreReady = false;
- }
- return mQuickContact;
- }
-
- /**
- * Returns the photo view, creating it if necessary.
- */
- public ImageView getPhotoView() {
- if (mPhotoView == null) {
- mPhotoView = new ImageView(mContext);
- mPhotoView.setLayoutParams(getDefaultPhotoLayoutParams());
- // Quick contact style used above will set a background - remove it
- mPhotoView.setBackground(null);
- addView(mPhotoView);
- mPhotoViewWidthAndHeightAreReady = false;
- }
- return mPhotoView;
- }
-
- /**
- * Removes the photo view.
- */
- public void removePhotoView() {
- removePhotoView(false, true);
- }
-
- /**
- * Removes the photo view.
- *
- * @param keepHorizontalPadding True means data on the right side will have
- * padding on left, pretending there is still a photo view.
- * @param keepVerticalPadding True means the View will have some height
- * enough for accommodating a photo view.
- */
- public void removePhotoView(boolean keepHorizontalPadding, boolean keepVerticalPadding) {
- mPhotoViewWidthAndHeightAreReady = false;
- mKeepHorizontalPaddingForPhotoView = keepHorizontalPadding;
- mKeepVerticalPaddingForPhotoView = keepVerticalPadding;
- if (mPhotoView != null) {
- removeView(mPhotoView);
- mPhotoView = null;
- }
- if (mQuickContact != null) {
- removeView(mQuickContact);
- mQuickContact = null;
- }
- }
-
- /**
- * Sets a word prefix that will be highlighted if encountered in fields like
- * name and search snippet.
- * <p>
- * NOTE: must be all upper-case
- */
- public void setHighlightedPrefix(char[] upperCasePrefix) {
- mHighlightedPrefix = upperCasePrefix;
- }
-
- /**
- * Returns the text view for the contact name, creating it if necessary.
- */
- public TextView getNameTextView() {
- if (mNameTextView == null) {
- mNameTextView = new TextView(mContext);
- mNameTextView.setSingleLine(true);
- mNameTextView.setEllipsize(getTextEllipsis());
- mNameTextView.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
- // Manually call setActivated() since this view may be added after the first
- // setActivated() call toward this whole item view.
- mNameTextView.setActivated(isActivated());
- mNameTextView.setGravity(Gravity.CENTER_VERTICAL);
- addView(mNameTextView);
- }
- return mNameTextView;
- }
-
- /**
- * Adds or updates a text view for the phonetic name.
- */
- public void setPhoneticName(char[] text, int size) {
- if (text == null || size == 0) {
- if (mPhoneticNameTextView != null) {
- mPhoneticNameTextView.setVisibility(View.GONE);
- }
- } else {
- getPhoneticNameTextView();
- setMarqueeText(mPhoneticNameTextView, text, size);
- mPhoneticNameTextView.setVisibility(VISIBLE);
- }
- }
-
- /**
- * Returns the text view for the phonetic name, creating it if necessary.
- */
- public TextView getPhoneticNameTextView() {
- if (mPhoneticNameTextView == null) {
- mPhoneticNameTextView = new TextView(mContext);
- mPhoneticNameTextView.setSingleLine(true);
- mPhoneticNameTextView.setEllipsize(getTextEllipsis());
- mPhoneticNameTextView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
- mPhoneticNameTextView.setTypeface(mPhoneticNameTextView.getTypeface(), Typeface.BOLD);
- mPhoneticNameTextView.setActivated(isActivated());
- addView(mPhoneticNameTextView);
- }
- return mPhoneticNameTextView;
- }
-
- /**
- * Adds or updates a text view for the data label.
- */
- public void setLabel(CharSequence text) {
- if (TextUtils.isEmpty(text)) {
- if (mLabelView != null) {
- mLabelView.setVisibility(View.GONE);
- }
- } else {
- getLabelView();
- setMarqueeText(mLabelView, text);
- mLabelView.setVisibility(VISIBLE);
- }
- }
-
- /**
- * Returns the text view for the data label, creating it if necessary.
- */
- public TextView getLabelView() {
- if (mLabelView == null) {
- mLabelView = new TextView(mContext);
- mLabelView.setSingleLine(true);
- mLabelView.setEllipsize(getTextEllipsis());
- mLabelView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
- if (mPhotoPosition == PhotoPosition.LEFT) {
- mLabelView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mCountViewTextSize);
- mLabelView.setAllCaps(true);
- mLabelView.setGravity(Gravity.RIGHT);
- } else {
- mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
- }
- mLabelView.setActivated(isActivated());
- addView(mLabelView);
- }
- return mLabelView;
- }
-
- /**
- * Adds or updates a text view for the data element.
- */
- public void setData(char[] text, int size) {
- if (text == null || size == 0) {
- if (mDataView != null) {
- mDataView.setVisibility(View.GONE);
- }
- } else {
- getDataView();
- setMarqueeText(mDataView, text, size);
- mDataView.setVisibility(VISIBLE);
- }
- }
-
- private void setMarqueeText(TextView textView, char[] text, int size) {
- if (getTextEllipsis() == TruncateAt.MARQUEE) {
- setMarqueeText(textView, new String(text, 0, size));
- } else {
- textView.setText(text, 0, size);
- }
- }
-
- private void setMarqueeText(TextView textView, CharSequence text) {
- if (getTextEllipsis() == TruncateAt.MARQUEE) {
- // To show MARQUEE correctly (with END effect during non-active state), we need
- // to build Spanned with MARQUEE in addition to TextView's ellipsize setting.
- final SpannableString spannable = new SpannableString(text);
- spannable.setSpan(TruncateAt.MARQUEE, 0, spannable.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- textView.setText(spannable);
- } else {
- textView.setText(text);
- }
- }
-
- /**
- * Returns the text view for the data text, creating it if necessary.
- */
- public TextView getDataView() {
- if (mDataView == null) {
- mDataView = new TextView(mContext);
- mDataView.setSingleLine(true);
- mDataView.setEllipsize(getTextEllipsis());
- mDataView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
- mDataView.setActivated(isActivated());
- addView(mDataView);
- }
- return mDataView;
- }
-
- /**
- * Adds or updates a text view for the search snippet.
- */
- public void setSnippet(String text) {
- if (TextUtils.isEmpty(text)) {
- if (mSnippetView != null) {
- mSnippetView.setVisibility(View.GONE);
- }
- } else {
- mPrefixHighlighter.setText(getSnippetView(), text, mHighlightedPrefix);
- mSnippetView.setVisibility(VISIBLE);
- }
- }
-
- /**
- * Returns the text view for the search snippet, creating it if necessary.
- */
- public TextView getSnippetView() {
- if (mSnippetView == null) {
- mSnippetView = new TextView(mContext);
- mSnippetView.setSingleLine(true);
- mSnippetView.setEllipsize(getTextEllipsis());
- mSnippetView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
- mSnippetView.setTypeface(mSnippetView.getTypeface(), Typeface.BOLD);
- mSnippetView.setActivated(isActivated());
- addView(mSnippetView);
- }
- return mSnippetView;
- }
-
- /**
- * Returns the text view for the status, creating it if necessary.
- */
- public TextView getStatusView() {
- if (mStatusView == null) {
- mStatusView = new TextView(mContext);
- mStatusView.setSingleLine(true);
- mStatusView.setEllipsize(getTextEllipsis());
- mStatusView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
- mStatusView.setTextColor(mSecondaryTextColor);
- mStatusView.setActivated(isActivated());
- addView(mStatusView);
- }
- return mStatusView;
- }
-
- /**
- * 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();
- setMarqueeText(mCountView, text);
- mCountView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCountViewTextSize);
- mCountView.setGravity(Gravity.CENTER_VERTICAL);
- mCountView.setTextColor(mContactsCountTextColor);
- mCountView.setVisibility(VISIBLE);
- }
- }
-
- /**
- * Adds or updates a text view for the status.
- */
- public void setStatus(CharSequence text) {
- if (TextUtils.isEmpty(text)) {
- if (mStatusView != null) {
- mStatusView.setVisibility(View.GONE);
- }
- } else {
- getStatusView();
- setMarqueeText(mStatusView, text);
- mStatusView.setVisibility(VISIBLE);
- }
- }
-
- /**
- * Adds or updates the presence icon view.
- */
- public void setPresence(Drawable icon) {
- if (icon != null) {
- if (mPresenceIcon == null) {
- mPresenceIcon = new ImageView(mContext);
- addView(mPresenceIcon);
- }
- mPresenceIcon.setImageDrawable(icon);
- mPresenceIcon.setScaleType(ScaleType.CENTER);
- mPresenceIcon.setVisibility(View.VISIBLE);
- } else {
- if (mPresenceIcon != null) {
- mPresenceIcon.setVisibility(View.GONE);
- }
- }
- }
-
- private TruncateAt getTextEllipsis() {
- return TruncateAt.MARQUEE;
- }
-
- public void showDisplayName(Cursor cursor, int nameColumnIndex, int displayOrder) {
- CharSequence name = cursor.getString(nameColumnIndex);
- if (!TextUtils.isEmpty(name)) {
- name = mPrefixHighlighter.apply(name, mHighlightedPrefix);
- } else {
- name = mUnknownNameText;
- }
- setMarqueeText(getNameTextView(), name);
-
- // Since the quick contact content description is derived from the display name and there is
- // no guarantee that when the quick contact is initialized the display name is already set,
- // do it here too.
- if (mQuickContact != null) {
- mQuickContact.setContentDescription(mContext.getString(
- R.string.description_quick_contact_for, mNameTextView.getText()));
- }
- }
-
- public void hideDisplayName() {
- if (mNameTextView != null) {
- removeView(mNameTextView);
- mNameTextView = null;
- }
- }
-
- public void showPhoneticName(Cursor cursor, int phoneticNameColumnIndex) {
- cursor.copyStringToBuffer(phoneticNameColumnIndex, mPhoneticNameBuffer);
- int phoneticNameSize = mPhoneticNameBuffer.sizeCopied;
- if (phoneticNameSize != 0) {
- setPhoneticName(mPhoneticNameBuffer.data, phoneticNameSize);
- } else {
- setPhoneticName(null, 0);
- }
- }
-
- public void hidePhoneticName() {
- if (mPhoneticNameTextView != null) {
- removeView(mPhoneticNameTextView);
- mPhoneticNameTextView = null;
- }
- }
-
- /**
- * Sets the proper icon (star or presence or nothing) and/or status message.
- */
- public void showPresenceAndStatusMessage(Cursor cursor, int presenceColumnIndex,
- int contactStatusColumnIndex) {
- Drawable icon = null;
- int presence = 0;
- if (!cursor.isNull(presenceColumnIndex)) {
- presence = cursor.getInt(presenceColumnIndex);
- icon = ContactPresenceIconUtil.getPresenceIcon(getContext(), presence);
- }
- 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);
- }
-
- /**
- * Shows search snippet.
- */
- public void showSnippet(Cursor cursor, int summarySnippetColumnIndex) {
- if (cursor.getColumnCount() <= summarySnippetColumnIndex) {
- setSnippet(null);
- return;
- }
- String snippet;
- String columnContent = cursor.getString(summarySnippetColumnIndex);
-
- // Do client side snippeting if provider didn't do it
- Bundle extras = cursor.getExtras();
- if (extras.getBoolean(ContactsContract.DEFERRED_SNIPPETING)) {
- int displayNameIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
-
- snippet = ContactsContract.snippetize(columnContent,
- displayNameIndex < 0 ? null : cursor.getString(displayNameIndex),
- extras.getString(ContactsContract.DEFERRED_SNIPPETING_QUERY),
- DefaultContactListAdapter.SNIPPET_START_MATCH,
- DefaultContactListAdapter.SNIPPET_END_MATCH,
- DefaultContactListAdapter.SNIPPET_ELLIPSIS,
- DefaultContactListAdapter.SNIPPET_MAX_TOKENS);
- } else {
- snippet = columnContent;
- }
-
- if (snippet != null) {
- int from = 0;
- int to = snippet.length();
- int start = snippet.indexOf(DefaultContactListAdapter.SNIPPET_START_MATCH);
- if (start == -1) {
- snippet = null;
- } else {
- int firstNl = snippet.lastIndexOf('\n', start);
- if (firstNl != -1) {
- from = firstNl + 1;
- }
- int end = snippet.lastIndexOf(DefaultContactListAdapter.SNIPPET_END_MATCH);
- if (end != -1) {
- int lastNl = snippet.indexOf('\n', end);
- if (lastNl != -1) {
- to = lastNl;
- }
- }
-
- StringBuilder sb = new StringBuilder();
- for (int i = from; i < to; i++) {
- char c = snippet.charAt(i);
- if (c != DefaultContactListAdapter.SNIPPET_START_MATCH &&
- c != DefaultContactListAdapter.SNIPPET_END_MATCH) {
- sb.append(c);
- }
- }
- snippet = sb.toString();
- }
- }
- setSnippet(snippet);
- }
-
- /**
- * Shows data element (e.g. phone number).
- */
- public void showData(Cursor cursor, int dataColumnIndex) {
- cursor.copyStringToBuffer(dataColumnIndex, mDataBuffer);
- setData(mDataBuffer.data, mDataBuffer.sizeCopied);
- }
-
- public void setActivatedStateSupported(boolean flag) {
- this.mActivatedStateSupported = flag;
- }
-
- @Override
- public void requestLayout() {
- // We will assume that once measured this will not need to resize
- // itself, so there is no need to pass the layout request to the parent
- // view (ListView).
- forceLayout();
- }
-
- public void setPhotoPosition(PhotoPosition photoPosition) {
- mPhotoPosition = photoPosition;
- }
-
- public PhotoPosition getPhotoPosition() {
- return mPhotoPosition;
- }
-
- /**
- * Specifies left and right margin for selection bounds. See also
- * {@link #adjustListItemSelectionBounds(Rect)}.
- */
- public void setSelectionBoundsHorizontalMargin(int left, int right) {
- mSelectionBoundsMarginLeft = left;
- mSelectionBoundsMarginRight = right;
- }
-}
diff --git a/src/com/android/contacts/list/ContactListPinnedHeaderView.java b/src/com/android/contacts/list/ContactListPinnedHeaderView.java
deleted file mode 100644
index 34a56d6..0000000
--- a/src/com/android/contacts/list/ContactListPinnedHeaderView.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2010 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 android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.Typeface;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.contacts.R;
-
-/**
- * A custom view for the pinned section header shown at the top of the contact list.
- */
-public class ContactListPinnedHeaderView extends ViewGroup {
-
- protected final Context mContext;
-
- private final int mHeaderTextColor;
- private final int mHeaderTextIndent;
- private final int mHeaderTextSize;
- private final int mHeaderUnderlineHeight;
- private final int mHeaderUnderlineColor;
- private final int mPaddingRight;
- private final int mPaddingLeft;
- private final int mContactsCountTextColor;
- private final int mCountViewTextSize;
-
- private int mHeaderBackgroundHeight;
- private TextView mHeaderTextView;
- private TextView mCountTextView = null;
- private View mHeaderDivider;
-
- public ContactListPinnedHeaderView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
-
- TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ContactListItemView);
-
- mHeaderTextIndent = a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_header_text_indent, 0);
- mHeaderTextColor = a.getColor(
- R.styleable.ContactListItemView_list_item_header_text_color, Color.BLACK);
- mHeaderTextSize = a.getDimensionPixelSize(
- R.styleable.ContactListItemView_list_item_header_text_size, 12);
- mHeaderUnderlineHeight = a.getDimensionPixelSize(
- R.styleable.ContactListItemView_list_item_header_underline_height, 1);
- mHeaderUnderlineColor = a.getColor(
- R.styleable.ContactListItemView_list_item_header_underline_color, 0);
- mHeaderBackgroundHeight = a.getDimensionPixelSize(
- R.styleable.ContactListItemView_list_item_header_height, 30);
- mPaddingLeft = a.getDimensionPixelOffset(
- 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);
- mCountViewTextSize = (int)a.getDimensionPixelSize(
- R.styleable.ContactListItemView_list_item_contacts_count_text_size, 12);
-
- a.recycle();
-
- mHeaderTextView = new TextView(mContext);
- mHeaderTextView.setTextColor(mHeaderTextColor);
- mHeaderTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mHeaderTextSize);
- mHeaderTextView.setTypeface(mHeaderTextView.getTypeface(), Typeface.BOLD);
- mHeaderTextView.setGravity(Gravity.CENTER_VERTICAL);
- mHeaderTextView.setAllCaps(true);
- addView(mHeaderTextView);
- mHeaderDivider = new View(mContext);
- mHeaderDivider.setBackgroundColor(mHeaderUnderlineColor);
- addView(mHeaderDivider);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
- // We will match parent's width and wrap content vertically.
- int width = resolveSize(0, widthMeasureSpec);
-
- mHeaderTextView.measure(
- 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);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- int width = right - left;
-
- // Take into account left and right padding when laying out the below views.
- mHeaderTextView.layout(mHeaderTextIndent + mPaddingLeft,
- 0,
- mHeaderTextView.getMeasuredWidth() + mHeaderTextIndent + mPaddingLeft,
- mHeaderBackgroundHeight);
-
- if (isViewMeasurable(mCountTextView)) {
- mCountTextView.layout(width - mPaddingRight - mCountTextView.getMeasuredWidth(),
- 0,
- width - mPaddingRight,
- mHeaderBackgroundHeight);
- }
-
- mHeaderDivider.layout(mPaddingLeft,
- mHeaderBackgroundHeight,
- width - mPaddingRight,
- mHeaderBackgroundHeight + mHeaderUnderlineHeight);
- }
-
- /**
- * Sets section header or makes it invisible if the title is null.
- */
- public void setSectionHeader(String title) {
- if (!TextUtils.isEmpty(title)) {
- mHeaderTextView.setText(title);
- mHeaderTextView.setVisibility(View.VISIBLE);
- mHeaderDivider.setVisibility(View.VISIBLE);
- } else {
- mHeaderTextView.setVisibility(View.GONE);
- mHeaderDivider.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void requestLayout() {
- // We will assume that once measured this will not need to resize
- // itself, so there is no need to pass the layout request to the parent
- // view (ListView).
- forceLayout();
- }
-
- public void setCountView(String count) {
- if (mCountTextView == null) {
- mCountTextView = new TextView(mContext);
- mCountTextView.setTextColor(mContactsCountTextColor);
- mCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCountViewTextSize);
- mCountTextView.setGravity(Gravity.CENTER_VERTICAL);
- 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/ContactNameHighlightingAnimation.java b/src/com/android/contacts/list/ContactNameHighlightingAnimation.java
index ddea603..46fe88b 100644
--- a/src/com/android/contacts/list/ContactNameHighlightingAnimation.java
+++ b/src/com/android/contacts/list/ContactNameHighlightingAnimation.java
@@ -18,6 +18,7 @@
import android.view.View;
import android.widget.ListView;
+import com.android.contacts.common.list.ContactListItemView;
import com.android.contacts.widget.TextHighlightingAnimation;
/**
diff --git a/src/com/android/contacts/list/ContactPickerFragment.java b/src/com/android/contacts/list/ContactPickerFragment.java
index bda62f3..2de71be 100644
--- a/src/com/android/contacts/list/ContactPickerFragment.java
+++ b/src/com/android/contacts/list/ContactPickerFragment.java
@@ -24,6 +24,11 @@
import android.widget.AdapterView;
import com.android.contacts.R;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListAdapter;
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.list.DefaultContactListAdapter;
+import com.android.contacts.common.list.DirectoryListLoader;
import com.android.contacts.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
/**
diff --git a/src/com/android/contacts/list/ContactsSectionIndexer.java b/src/com/android/contacts/list/ContactsSectionIndexer.java
deleted file mode 100644
index c260667..0000000
--- a/src/com/android/contacts/list/ContactsSectionIndexer.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2010 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 android.text.TextUtils;
-import android.widget.SectionIndexer;
-
-import java.util.Arrays;
-
-/**
- * A section indexer that is configured with precomputed section titles and
- * their respective counts.
- */
-public class ContactsSectionIndexer implements SectionIndexer {
-
- private String[] mSections;
- private int[] mPositions;
- private int mCount;
- private static final String BLANK_HEADER_STRING = " ";
-
- /**
- * Constructor.
- *
- * @param sections a non-null array
- * @param counts a non-null array of the same size as <code>sections</code>
- */
- public ContactsSectionIndexer(String[] sections, int[] counts) {
- if (sections == null || counts == null) {
- throw new NullPointerException();
- }
-
- if (sections.length != counts.length) {
- throw new IllegalArgumentException(
- "The sections and counts arrays must have the same length");
- }
-
- // TODO process sections/counts based on current locale and/or specific section titles
-
- this.mSections = sections;
- mPositions = new int[counts.length];
- int position = 0;
- for (int i = 0; i < counts.length; i++) {
- if (TextUtils.isEmpty(mSections[i])) {
- mSections[i] = BLANK_HEADER_STRING;
- } else if (!mSections[i].equals(BLANK_HEADER_STRING)) {
- mSections[i] = mSections[i].trim();
- }
-
- mPositions[i] = position;
- position += counts[i];
- }
- mCount = position;
- }
-
- public Object[] getSections() {
- return mSections;
- }
-
- public int getPositionForSection(int section) {
- if (section < 0 || section >= mSections.length) {
- return -1;
- }
-
- return mPositions[section];
- }
-
- public int getSectionForPosition(int position) {
- if (position < 0 || position >= mCount) {
- return -1;
- }
-
- int index = Arrays.binarySearch(mPositions, position);
-
- /*
- * Consider this example: section positions are 0, 3, 5; the supplied
- * position is 4. The section corresponding to position 4 starts at
- * position 3, so the expected return value is 1. Binary search will not
- * find 4 in the array and thus will return -insertPosition-1, i.e. -3.
- * To get from that number to the expected value of 1 we need to negate
- * and subtract 2.
- */
- return index >= 0 ? index : -index - 2;
- }
-
- public void setProfileHeader(String header) {
- if (mSections != null) {
- // Don't do anything if the header is already set properly.
- if (mSections.length > 0 && header.equals(mSections[0])) {
- return;
- }
-
- // Since the section indexer isn't aware of the profile at the top, we need to add a
- // special section at the top for it and shift everything else down.
- String[] tempSections = new String[mSections.length + 1];
- int[] tempPositions = new int[mPositions.length + 1];
- tempSections[0] = header;
- tempPositions[0] = 0;
- for (int i = 1; i <= mPositions.length; i++) {
- tempSections[i] = mSections[i - 1];
- tempPositions[i] = mPositions[i - 1] + 1;
- }
- mSections = tempSections;
- mPositions = tempPositions;
- mCount++;
- }
- }
-}
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 8f38045..c9c895b 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -32,6 +32,10 @@
import android.widget.TextView;
import com.android.contacts.R;
+import com.android.contacts.common.list.ContactListAdapter;
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.list.DefaultContactListAdapter;
+import com.android.contacts.common.list.ProfileAndContactsLoader;
import com.android.contacts.editor.ContactEditorFragment;
import com.android.contacts.util.AccountFilterUtil;
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
deleted file mode 100644
index 88c1db0..0000000
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2010 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 android.content.ContentUris;
-import android.content.Context;
-import android.content.CursorLoader;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.net.Uri;
-import android.net.Uri.Builder;
-import android.preference.PreferenceManager;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Directory;
-import android.provider.ContactsContract.SearchSnippetColumns;
-import android.text.TextUtils;
-import android.view.View;
-
-import com.android.contacts.preference.ContactsPreferences;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
- */
-public class DefaultContactListAdapter extends ContactListAdapter {
-
- public static final char SNIPPET_START_MATCH = '\u0001';
- public static final char SNIPPET_END_MATCH = '\u0001';
- public static final String SNIPPET_ELLIPSIS = "\u2026";
- public static final int SNIPPET_MAX_TOKENS = 5;
-
- public static final String SNIPPET_ARGS = SNIPPET_START_MATCH + "," + SNIPPET_END_MATCH + ","
- + SNIPPET_ELLIPSIS + "," + SNIPPET_MAX_TOKENS;
-
- public DefaultContactListAdapter(Context context) {
- super(context);
- }
-
- @Override
- public void configureLoader(CursorLoader loader, long directoryId) {
- if (loader instanceof ProfileAndContactsLoader) {
- ((ProfileAndContactsLoader) loader).setLoadProfile(shouldIncludeProfile());
- }
-
- ContactListFilter filter = getFilter();
- if (isSearchMode()) {
- String query = getQueryString();
- if (query == null) {
- query = "";
- }
- query = query.trim();
- if (TextUtils.isEmpty(query)) {
- // Regardless of the directory, we don't want anything returned,
- // so let's just send a "nothing" query to the local directory.
- loader.setUri(Contacts.CONTENT_URI);
- loader.setProjection(getProjection(false));
- loader.setSelection("0");
- } else {
- Builder builder = Contacts.CONTENT_FILTER_URI.buildUpon();
- builder.appendPath(query); // Builder will encode the query
- builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
- String.valueOf(directoryId));
- if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE) {
- builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
- String.valueOf(getDirectoryResultLimit()));
- }
- builder.appendQueryParameter(SearchSnippetColumns.SNIPPET_ARGS_PARAM_KEY,
- SNIPPET_ARGS);
- builder.appendQueryParameter(SearchSnippetColumns.DEFERRED_SNIPPETING_KEY,"1");
- loader.setUri(builder.build());
- loader.setProjection(getProjection(true));
- }
- } else {
- configureUri(loader, directoryId, filter);
- loader.setProjection(getProjection(false));
- configureSelection(loader, directoryId, filter);
- }
-
- String sortOrder;
- if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
- sortOrder = Contacts.SORT_KEY_PRIMARY;
- } else {
- sortOrder = Contacts.SORT_KEY_ALTERNATIVE;
- }
-
- loader.setSortOrder(sortOrder);
- }
-
- protected void configureUri(CursorLoader loader, long directoryId, ContactListFilter filter) {
- Uri uri = Contacts.CONTENT_URI;
- if (filter != null && filter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
- String lookupKey = getSelectedContactLookupKey();
- if (lookupKey != null) {
- uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
- } else {
- uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, getSelectedContactId());
- }
- }
-
- if (directoryId == Directory.DEFAULT && isSectionHeaderDisplayEnabled()) {
- uri = buildSectionIndexerUri(uri);
- }
-
- // The "All accounts" filter is the same as the entire contents of Directory.DEFAULT
- if (filter != null
- && filter.filterType != ContactListFilter.FILTER_TYPE_CUSTOM
- && filter.filterType != ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
- final Uri.Builder builder = uri.buildUpon();
- builder.appendQueryParameter(
- ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT));
- if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) {
- filter.addAccountQueryParameterToUrl(builder);
- }
- uri = builder.build();
- }
-
- loader.setUri(uri);
- }
-
- private void configureSelection(
- CursorLoader loader, long directoryId, ContactListFilter filter) {
- if (filter == null) {
- return;
- }
-
- if (directoryId != Directory.DEFAULT) {
- return;
- }
-
- StringBuilder selection = new StringBuilder();
- List<String> selectionArgs = new ArrayList<String>();
-
- switch (filter.filterType) {
- case ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS: {
- // We have already added directory=0 to the URI, which takes care of this
- // filter
- break;
- }
- case ContactListFilter.FILTER_TYPE_SINGLE_CONTACT: {
- // We have already added the lookup key to the URI, which takes care of this
- // filter
- break;
- }
- case ContactListFilter.FILTER_TYPE_STARRED: {
- selection.append(Contacts.STARRED + "!=0");
- break;
- }
- case ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY: {
- selection.append(Contacts.HAS_PHONE_NUMBER + "=1");
- break;
- }
- case ContactListFilter.FILTER_TYPE_CUSTOM: {
- selection.append(Contacts.IN_VISIBLE_GROUP + "=1");
- if (isCustomFilterForPhoneNumbersOnly()) {
- selection.append(" AND " + Contacts.HAS_PHONE_NUMBER + "=1");
- }
- break;
- }
- case ContactListFilter.FILTER_TYPE_ACCOUNT: {
- // We use query parameters for account filter, so no selection to add here.
- break;
- }
- }
- loader.setSelection(selection.toString());
- loader.setSelectionArgs(selectionArgs.toArray(new String[0]));
- }
-
- @Override
- protected void bindView(View itemView, int partition, Cursor cursor, int position) {
- final ContactListItemView view = (ContactListItemView)itemView;
-
- view.setHighlightedPrefix(isSearchMode() ? getUpperCaseQueryString() : null);
-
- if (isSelectionVisible()) {
- view.setActivated(isSelectedContact(partition, cursor));
- }
-
- bindSectionHeaderAndDivider(view, position, cursor);
-
- if (isQuickContactEnabled()) {
- bindQuickContact(view, partition, cursor, ContactQuery.CONTACT_PHOTO_ID,
- ContactQuery.CONTACT_PHOTO_URI, ContactQuery.CONTACT_ID,
- ContactQuery.CONTACT_LOOKUP_KEY);
- } else {
- if (getDisplayPhotos()) {
- bindPhoto(view, partition, cursor);
- }
- }
-
- bindName(view, cursor);
- bindPresenceAndStatusMessage(view, cursor);
-
- if (isSearchMode()) {
- bindSearchSnippet(view, cursor);
- } else {
- view.setSnippet(null);
- }
- }
-
- private boolean isCustomFilterForPhoneNumbersOnly() {
- // TODO: this flag should not be stored in shared prefs. It needs to be in the db.
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
- return prefs.getBoolean(ContactsPreferences.PREF_DISPLAY_ONLY_PHONES,
- ContactsPreferences.PREF_DISPLAY_ONLY_PHONES_DEFAULT);
- }
-}
diff --git a/src/com/android/contacts/list/DirectoryListLoader.java b/src/com/android/contacts/list/DirectoryListLoader.java
deleted file mode 100644
index f85cc5e..0000000
--- a/src/com/android/contacts/list/DirectoryListLoader.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2010 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 android.content.AsyncTaskLoader;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.ContactsContract.Directory;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.contacts.R;
-
-/**
- * A specialized loader for the list of directories, see {@link Directory}.
- */
-public class DirectoryListLoader extends AsyncTaskLoader<Cursor> {
-
- private static final String TAG = "ContactEntryListAdapter";
-
- public static final int SEARCH_MODE_NONE = 0;
- public static final int SEARCH_MODE_DEFAULT = 1;
- public static final int SEARCH_MODE_CONTACT_SHORTCUT = 2;
- public static final int SEARCH_MODE_DATA_SHORTCUT = 3;
-
- private static final class DirectoryQuery {
- public static final Uri URI = Directory.CONTENT_URI;
- public static final String ORDER_BY = Directory._ID;
-
- public static final String[] PROJECTION = {
- Directory._ID,
- Directory.PACKAGE_NAME,
- Directory.TYPE_RESOURCE_ID,
- Directory.DISPLAY_NAME,
- Directory.PHOTO_SUPPORT,
- };
-
- public static final int ID = 0;
- public static final int PACKAGE_NAME = 1;
- public static final int TYPE_RESOURCE_ID = 2;
- public static final int DISPLAY_NAME = 3;
- public static final int PHOTO_SUPPORT = 4;
- }
-
- public static final String DIRECTORY_TYPE = "directoryType";
-
- private static final String[] RESULT_PROJECTION = {
- Directory._ID,
- DIRECTORY_TYPE,
- Directory.DISPLAY_NAME,
- Directory.PHOTO_SUPPORT,
- };
-
- private final ContentObserver mObserver = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange) {
- forceLoad();
- }
- };
-
- private int mDirectorySearchMode;
- private boolean mLocalInvisibleDirectoryEnabled;
-
- private MatrixCursor mDefaultDirectoryList;
-
- public DirectoryListLoader(Context context) {
- super(context);
- }
-
- public void setDirectorySearchMode(int mode) {
- mDirectorySearchMode = mode;
- }
-
- /**
- * A flag that indicates whether the {@link Directory#LOCAL_INVISIBLE} directory should
- * be included in the results.
- */
- public void setLocalInvisibleDirectoryEnabled(boolean flag) {
- this.mLocalInvisibleDirectoryEnabled = flag;
- }
-
- @Override
- protected void onStartLoading() {
- getContext().getContentResolver().
- registerContentObserver(Directory.CONTENT_URI, false, mObserver);
- forceLoad();
- }
-
- @Override
- protected void onStopLoading() {
- getContext().getContentResolver().unregisterContentObserver(mObserver);
- }
-
- @Override
- public Cursor loadInBackground() {
- if (mDirectorySearchMode == SEARCH_MODE_NONE) {
- return getDefaultDirectories();
- }
-
- MatrixCursor result = new MatrixCursor(RESULT_PROJECTION);
- Context context = getContext();
- PackageManager pm = context.getPackageManager();
- String selection;
- switch (mDirectorySearchMode) {
- case SEARCH_MODE_DEFAULT:
- selection = mLocalInvisibleDirectoryEnabled ? null
- : (Directory._ID + "!=" + Directory.LOCAL_INVISIBLE);
- break;
-
- case SEARCH_MODE_CONTACT_SHORTCUT:
- selection = Directory.SHORTCUT_SUPPORT + "=" + Directory.SHORTCUT_SUPPORT_FULL
- + (mLocalInvisibleDirectoryEnabled ? ""
- : (" AND " + Directory._ID + "!=" + Directory.LOCAL_INVISIBLE));
- break;
-
- case SEARCH_MODE_DATA_SHORTCUT:
- selection = Directory.SHORTCUT_SUPPORT + " IN ("
- + Directory.SHORTCUT_SUPPORT_FULL + ", "
- + Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY + ")"
- + (mLocalInvisibleDirectoryEnabled ? ""
- : (" AND " + Directory._ID + "!=" + Directory.LOCAL_INVISIBLE));
- break;
-
- default:
- throw new RuntimeException(
- "Unsupported directory search mode: " + mDirectorySearchMode);
- }
-
- Cursor cursor = context.getContentResolver().query(DirectoryQuery.URI,
- DirectoryQuery.PROJECTION, selection, null, DirectoryQuery.ORDER_BY);
- try {
- while(cursor.moveToNext()) {
- long directoryId = cursor.getLong(DirectoryQuery.ID);
- String directoryType = null;
-
- String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
- int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
- if (!TextUtils.isEmpty(packageName) && typeResourceId != 0) {
- try {
- directoryType = pm.getResourcesForApplication(packageName)
- .getString(typeResourceId);
- } catch (Exception e) {
- Log.e(TAG, "Cannot obtain directory type from package: " + packageName);
- }
- }
- String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
- int photoSupport = cursor.getInt(DirectoryQuery.PHOTO_SUPPORT);
- result.addRow(new Object[]{directoryId, directoryType, displayName, photoSupport});
- }
- } finally {
- cursor.close();
- }
-
- return result;
- }
-
- private Cursor getDefaultDirectories() {
- if (mDefaultDirectoryList == null) {
- mDefaultDirectoryList = new MatrixCursor(RESULT_PROJECTION);
- mDefaultDirectoryList.addRow(new Object[] {
- Directory.DEFAULT,
- getContext().getString(R.string.contactsList),
- null
- });
- mDefaultDirectoryList.addRow(new Object[] {
- Directory.LOCAL_INVISIBLE,
- getContext().getString(R.string.local_invisible_directory),
- null
- });
- }
- return mDefaultDirectoryList;
- }
-
- @Override
- protected void onReset() {
- stopLoading();
- }
-}
diff --git a/src/com/android/contacts/list/DirectoryPartition.java b/src/com/android/contacts/list/DirectoryPartition.java
deleted file mode 100644
index c1fd533..0000000
--- a/src/com/android/contacts/list/DirectoryPartition.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2010 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 android.provider.ContactsContract.Directory;
-
-import com.android.common.widget.CompositeCursorAdapter;
-
-/**
- * Model object for a {@link Directory} row.
- */
-public final class DirectoryPartition extends CompositeCursorAdapter.Partition {
-
- public static final int STATUS_NOT_LOADED = 0;
- public static final int STATUS_LOADING = 1;
- public static final int STATUS_LOADED = 2;
-
- private long mDirectoryId;
- private String mDirectoryType;
- private String mDisplayName;
- private int mStatus;
- private boolean mPriorityDirectory;
- private boolean mPhotoSupported;
-
- public DirectoryPartition(boolean showIfEmpty, boolean hasHeader) {
- super(showIfEmpty, hasHeader);
- }
-
- /**
- * Directory ID, see {@link Directory}.
- */
- public long getDirectoryId() {
- return mDirectoryId;
- }
-
- public void setDirectoryId(long directoryId) {
- this.mDirectoryId = directoryId;
- }
-
- /**
- * Directory type resolved from {@link Directory#PACKAGE_NAME} and
- * {@link Directory#TYPE_RESOURCE_ID};
- */
- public String getDirectoryType() {
- return mDirectoryType;
- }
-
- public void setDirectoryType(String directoryType) {
- this.mDirectoryType = directoryType;
- }
-
- /**
- * See {@link Directory#DISPLAY_NAME}.
- */
- public String getDisplayName() {
- return mDisplayName;
- }
-
- public void setDisplayName(String displayName) {
- this.mDisplayName = displayName;
- }
-
- public int getStatus() {
- return mStatus;
- }
-
- public void setStatus(int status) {
- mStatus = status;
- }
-
- public boolean isLoading() {
- return mStatus == STATUS_NOT_LOADED || mStatus == STATUS_LOADING;
- }
-
- /**
- * Returns true if this directory should be loaded before non-priority directories.
- */
- public boolean isPriorityDirectory() {
- return mPriorityDirectory;
- }
-
- public void setPriorityDirectory(boolean priorityDirectory) {
- mPriorityDirectory = priorityDirectory;
- }
-
- /**
- * Returns true if this directory supports photos.
- */
- public boolean isPhotoSupported() {
- return mPhotoSupported;
- }
-
- public void setPhotoSupported(boolean flag) {
- this.mPhotoSupported = flag;
- }
-}
diff --git a/src/com/android/contacts/list/EmailAddressListAdapter.java b/src/com/android/contacts/list/EmailAddressListAdapter.java
index c85abdd..4a32ae3 100644
--- a/src/com/android/contacts/list/EmailAddressListAdapter.java
+++ b/src/com/android/contacts/list/EmailAddressListAdapter.java
@@ -29,6 +29,9 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+
/**
* A cursor adapter for the {@link Email#CONTENT_TYPE} content type.
*/
diff --git a/src/com/android/contacts/list/EmailAddressPickerFragment.java b/src/com/android/contacts/list/EmailAddressPickerFragment.java
index 41f470f..f4dd108 100644
--- a/src/com/android/contacts/list/EmailAddressPickerFragment.java
+++ b/src/com/android/contacts/list/EmailAddressPickerFragment.java
@@ -21,6 +21,8 @@
import android.view.ViewGroup;
import com.android.contacts.R;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.DirectoryListLoader;
/**
* Fragment containing an email list for picking.
diff --git a/src/com/android/contacts/list/JoinContactListAdapter.java b/src/com/android/contacts/list/JoinContactListAdapter.java
index 6e1a6b2..88259c1 100644
--- a/src/com/android/contacts/list/JoinContactListAdapter.java
+++ b/src/com/android/contacts/list/JoinContactListAdapter.java
@@ -31,6 +31,9 @@
import android.widget.TextView;
import com.android.contacts.R;
+import com.android.contacts.common.list.ContactListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+import com.android.contacts.common.list.DirectoryListLoader;
public class JoinContactListAdapter extends ContactListAdapter {
diff --git a/src/com/android/contacts/list/LegacyContactListAdapter.java b/src/com/android/contacts/list/LegacyContactListAdapter.java
index dbdce5e..defc4e7 100644
--- a/src/com/android/contacts/list/LegacyContactListAdapter.java
+++ b/src/com/android/contacts/list/LegacyContactListAdapter.java
@@ -24,6 +24,9 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+
/**
* A cursor adapter for the People.CONTENT_TYPE content type.
*/
diff --git a/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java b/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
index 1ef106a..bc16a0b 100644
--- a/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
@@ -26,6 +26,9 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+
/**
* A cursor adapter for the Phones.CONTENT_TYPE content type.
*/
diff --git a/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java b/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
index 91a0e3b..2819ed6 100644
--- a/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
+++ b/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
@@ -26,6 +26,9 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+
/**
* A cursor adapter for the ContactMethods.CONTENT_TYPE content type.
*/
diff --git a/src/com/android/contacts/list/PhoneNumberListAdapter.java b/src/com/android/contacts/list/PhoneNumberListAdapter.java
index 6323cc1..169ceff 100644
--- a/src/com/android/contacts/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/PhoneNumberListAdapter.java
@@ -35,6 +35,9 @@
import android.view.ViewGroup;
import com.android.contacts.R;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.list.ContactListItemView;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/contacts/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
index 17b5500..d029348 100644
--- a/src/com/android/contacts/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
@@ -28,6 +28,10 @@
import android.view.ViewGroup;
import com.android.contacts.R;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.list.ContactListItemView;
+import com.android.contacts.common.list.DirectoryListLoader;
import com.android.contacts.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
import com.android.contacts.util.AccountFilterUtil;
diff --git a/src/com/android/contacts/list/PostalAddressListAdapter.java b/src/com/android/contacts/list/PostalAddressListAdapter.java
index 5e3be30..a800c00 100644
--- a/src/com/android/contacts/list/PostalAddressListAdapter.java
+++ b/src/com/android/contacts/list/PostalAddressListAdapter.java
@@ -28,6 +28,9 @@
import android.view.View;
import android.view.ViewGroup;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+
/**
* A cursor adapter for the {@link StructuredPostal#CONTENT_TYPE} content type.
*/
diff --git a/src/com/android/contacts/list/PostalAddressPickerFragment.java b/src/com/android/contacts/list/PostalAddressPickerFragment.java
index 9bd4fc3..a874708 100644
--- a/src/com/android/contacts/list/PostalAddressPickerFragment.java
+++ b/src/com/android/contacts/list/PostalAddressPickerFragment.java
@@ -21,6 +21,8 @@
import android.view.ViewGroup;
import com.android.contacts.R;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.DirectoryListLoader;
/**
* Fragment containing a postal address list for picking.
diff --git a/src/com/android/contacts/list/ProfileAndContactsLoader.java b/src/com/android/contacts/list/ProfileAndContactsLoader.java
deleted file mode 100644
index 4291a40..0000000
--- a/src/com/android/contacts/list/ProfileAndContactsLoader.java
+++ /dev/null
@@ -1,90 +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 android.content.Context;
-import android.content.CursorLoader;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MergeCursor;
-import android.os.Bundle;
-import android.provider.ContactsContract.Profile;
-
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-/**
- * A loader for use in the default contact list, which will also query for the user's profile
- * if configured to do so.
- */
-public class ProfileAndContactsLoader extends CursorLoader {
-
- private boolean mLoadProfile;
- private String[] mProjection;
-
- public ProfileAndContactsLoader(Context context) {
- super(context);
- }
-
- public void setLoadProfile(boolean flag) {
- mLoadProfile = flag;
- }
-
- public void setProjection(String[] projection) {
- super.setProjection(projection);
- mProjection = projection;
- }
-
- @Override
- public Cursor loadInBackground() {
- // First load the profile, if enabled.
- List<Cursor> cursors = Lists.newArrayList();
- if (mLoadProfile) {
- cursors.add(loadProfile());
- }
- final Cursor contactsCursor = super.loadInBackground();
- cursors.add(contactsCursor);
- return new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) {
- @Override
- public Bundle getExtras() {
- // Need to get the extras from the contacts cursor.
- return contactsCursor.getExtras();
- }
- };
- }
-
- /**
- * Loads the profile into a MatrixCursor.
- */
- private MatrixCursor loadProfile() {
- Cursor cursor = getContext().getContentResolver().query(Profile.CONTENT_URI, mProjection,
- null, null, null);
- try {
- MatrixCursor matrix = new MatrixCursor(mProjection);
- Object[] row = new Object[mProjection.length];
- while (cursor.moveToNext()) {
- for (int i = 0; i < row.length; i++) {
- row[i] = cursor.getString(i);
- }
- matrix.addRow(row);
- }
- return matrix;
- } finally {
- cursor.close();
- }
- }
-}
diff --git a/src/com/android/contacts/preference/ContactsPreferences.java b/src/com/android/contacts/preference/ContactsPreferences.java
deleted file mode 100644
index e88417f..0000000
--- a/src/com/android/contacts/preference/ContactsPreferences.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2010 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.preference;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.provider.ContactsContract;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-
-import com.android.contacts.R;
-
-/**
- * Manages user preferences for contacts.
- */
-public final class ContactsPreferences extends ContentObserver {
-
- public static final String PREF_DISPLAY_ONLY_PHONES = "only_phones";
- public static final boolean PREF_DISPLAY_ONLY_PHONES_DEFAULT = false;
-
- private Context mContext;
- private int mSortOrder = -1;
- private int mDisplayOrder = -1;
- private ChangeListener mListener = null;
- private Handler mHandler;
-
- public ContactsPreferences(Context context) {
- super(null);
- mContext = context;
- mHandler = new Handler();
- }
-
- public boolean isSortOrderUserChangeable() {
- return mContext.getResources().getBoolean(R.bool.config_sort_order_user_changeable);
- }
-
- public int getDefaultSortOrder() {
- if (mContext.getResources().getBoolean(R.bool.config_default_sort_order_primary)) {
- return ContactsContract.Preferences.SORT_ORDER_PRIMARY;
- } else {
- return ContactsContract.Preferences.SORT_ORDER_ALTERNATIVE;
- }
- }
-
- public int getSortOrder() {
- if (!isSortOrderUserChangeable()) {
- return getDefaultSortOrder();
- }
-
- if (mSortOrder == -1) {
- try {
- mSortOrder = Settings.System.getInt(mContext.getContentResolver(),
- ContactsContract.Preferences.SORT_ORDER);
- } catch (SettingNotFoundException e) {
- mSortOrder = getDefaultSortOrder();
- }
- }
- return mSortOrder;
- }
-
- public void setSortOrder(int sortOrder) {
- mSortOrder = sortOrder;
- Settings.System.putInt(mContext.getContentResolver(),
- ContactsContract.Preferences.SORT_ORDER, sortOrder);
- }
-
- public boolean isDisplayOrderUserChangeable() {
- return mContext.getResources().getBoolean(R.bool.config_display_order_user_changeable);
- }
-
- public int getDefaultDisplayOrder() {
- if (mContext.getResources().getBoolean(R.bool.config_default_display_order_primary)) {
- return ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY;
- } else {
- return ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE;
- }
- }
-
- public int getDisplayOrder() {
- if (!isDisplayOrderUserChangeable()) {
- return getDefaultDisplayOrder();
- }
-
- if (mDisplayOrder == -1) {
- try {
- mDisplayOrder = Settings.System.getInt(mContext.getContentResolver(),
- ContactsContract.Preferences.DISPLAY_ORDER);
- } catch (SettingNotFoundException e) {
- mDisplayOrder = getDefaultDisplayOrder();
- }
- }
- return mDisplayOrder;
- }
-
- public void setDisplayOrder(int displayOrder) {
- mDisplayOrder = displayOrder;
- Settings.System.putInt(mContext.getContentResolver(),
- ContactsContract.Preferences.DISPLAY_ORDER, displayOrder);
- }
-
- public void registerChangeListener(ChangeListener listener) {
- if (mListener != null) unregisterChangeListener();
-
- mListener = listener;
-
- // Reset preferences to "unknown" because they may have changed while the
- // observer was unregistered.
- mDisplayOrder = -1;
- mSortOrder = -1;
-
- final ContentResolver contentResolver = mContext.getContentResolver();
- contentResolver.registerContentObserver(
- Settings.System.getUriFor(
- ContactsContract.Preferences.SORT_ORDER), false, this);
- contentResolver.registerContentObserver(
- Settings.System.getUriFor(
- ContactsContract.Preferences.DISPLAY_ORDER), false, this);
- }
-
- public void unregisterChangeListener() {
- if (mListener != null) {
- mContext.getContentResolver().unregisterContentObserver(this);
- mListener = null;
- }
- }
-
- @Override
- public void onChange(boolean selfChange) {
- // This notification is not sent on the Ui thread. Use the previously created Handler
- // to switch to the Ui thread
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mSortOrder = -1;
- mDisplayOrder = -1;
- if (mListener != null) mListener.onChange();
- }
- });
- }
-
- public interface ChangeListener {
- void onChange();
- }
-}
diff --git a/src/com/android/contacts/preference/DisplayOrderPreference.java b/src/com/android/contacts/preference/DisplayOrderPreference.java
index 8629384..81489a0 100644
--- a/src/com/android/contacts/preference/DisplayOrderPreference.java
+++ b/src/com/android/contacts/preference/DisplayOrderPreference.java
@@ -23,6 +23,7 @@
import android.util.AttributeSet;
import com.android.contacts.R;
+import com.android.contacts.common.preference.ContactsPreferences;
/**
* Custom preference: view-name-as (first name first or last name first).
diff --git a/src/com/android/contacts/preference/SortOrderPreference.java b/src/com/android/contacts/preference/SortOrderPreference.java
index 6ad4aad..da51eed 100644
--- a/src/com/android/contacts/preference/SortOrderPreference.java
+++ b/src/com/android/contacts/preference/SortOrderPreference.java
@@ -23,6 +23,7 @@
import android.util.AttributeSet;
import com.android.contacts.R;
+import com.android.contacts.common.preference.ContactsPreferences;
/**
* Custom preference: sort-by.
diff --git a/src/com/android/contacts/util/AccountFilterUtil.java b/src/com/android/contacts/util/AccountFilterUtil.java
index b95972c..a6df994 100644
--- a/src/com/android/contacts/util/AccountFilterUtil.java
+++ b/src/com/android/contacts/util/AccountFilterUtil.java
@@ -26,7 +26,7 @@
import com.android.contacts.R;
import com.android.contacts.list.AccountFilterActivity;
-import com.android.contacts.list.ContactListFilter;
+import com.android.contacts.common.list.ContactListFilter;
import com.android.contacts.list.ContactListFilterController;
/**
diff --git a/src/com/android/contacts/widget/AutoScrollListView.java b/src/com/android/contacts/widget/AutoScrollListView.java
deleted file mode 100644
index e9c1c42..0000000
--- a/src/com/android/contacts/widget/AutoScrollListView.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2010 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.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.ListView;
-
-/**
- * A ListView that can be asked to scroll (smoothly or otherwise) to a specific
- * position. This class takes advantage of similar functionality that exists
- * in {@link ListView} and enhances it.
- */
-public class AutoScrollListView extends ListView {
-
- /**
- * Position the element at about 1/3 of the list height
- */
- private static final float PREFERRED_SELECTION_OFFSET_FROM_TOP = 0.33f;
-
- private int mRequestedScrollPosition = -1;
- private boolean mSmoothScrollRequested;
-
- public AutoScrollListView(Context context) {
- super(context);
- }
-
- public AutoScrollListView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public AutoScrollListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- /**
- * Brings the specified position to view by optionally performing a jump-scroll maneuver:
- * first it jumps to some position near the one requested and then does a smooth
- * scroll to the requested position. This creates an impression of full smooth
- * scrolling without actually traversing the entire list. If smooth scrolling is
- * not requested, instantly positions the requested item at a preferred offset.
- */
- public void requestPositionToScreen(int position, boolean smoothScroll) {
- mRequestedScrollPosition = position;
- mSmoothScrollRequested = smoothScroll;
- requestLayout();
- }
-
- @Override
- protected void layoutChildren() {
- super.layoutChildren();
- if (mRequestedScrollPosition == -1) {
- return;
- }
-
- final int position = mRequestedScrollPosition;
- mRequestedScrollPosition = -1;
-
- int firstPosition = getFirstVisiblePosition() + 1;
- int lastPosition = getLastVisiblePosition();
- if (position >= firstPosition && position <= lastPosition) {
- return; // Already on screen
- }
-
- final int offset = (int) (getHeight() * PREFERRED_SELECTION_OFFSET_FROM_TOP);
- if (!mSmoothScrollRequested) {
- setSelectionFromTop(position, offset);
-
- // Since we have changed the scrolling position, we need to redo child layout
- // Calling "requestLayout" in the middle of a layout pass has no effect,
- // so we call layoutChildren explicitly
- super.layoutChildren();
-
- } else {
- // We will first position the list a couple of screens before or after
- // the new selection and then scroll smoothly to it.
- int twoScreens = (lastPosition - firstPosition) * 2;
- int preliminaryPosition;
- if (position < firstPosition) {
- preliminaryPosition = position + twoScreens;
- if (preliminaryPosition >= getCount()) {
- preliminaryPosition = getCount() - 1;
- }
- if (preliminaryPosition < firstPosition) {
- setSelection(preliminaryPosition);
- super.layoutChildren();
- }
- } else {
- preliminaryPosition = position - twoScreens;
- if (preliminaryPosition < 0) {
- preliminaryPosition = 0;
- }
- if (preliminaryPosition > lastPosition) {
- setSelection(preliminaryPosition);
- super.layoutChildren();
- }
- }
-
-
- smoothScrollToPositionFromTop(position, offset);
- }
- }
-}
diff --git a/src/com/android/contacts/widget/IndexerListAdapter.java b/src/com/android/contacts/widget/IndexerListAdapter.java
deleted file mode 100644
index a5aeb0f..0000000
--- a/src/com/android/contacts/widget/IndexerListAdapter.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2010 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.widget;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-import android.widget.SectionIndexer;
-
-/**
- * A list adapter that supports section indexer and a pinned header.
- */
-public abstract class IndexerListAdapter extends PinnedHeaderListAdapter implements SectionIndexer {
-
- protected Context mContext;
- private SectionIndexer mIndexer;
- private int mIndexedPartition = 0;
- private boolean mSectionHeaderDisplayEnabled;
- private View mHeader;
-
- /**
- * An item view is displayed differently depending on whether it is placed
- * at the beginning, middle or end of a section. It also needs to know the
- * section header when it is at the beginning of a section. This object
- * captures all this configuration.
- */
- public static final class Placement {
- private int position = ListView.INVALID_POSITION;
- public boolean firstInSection;
- public boolean lastInSection;
- public String sectionHeader;
-
- public void invalidate() {
- position = ListView.INVALID_POSITION;
- }
- }
-
- private Placement mPlacementCache = new Placement();
-
- /**
- * Constructor.
- */
- public IndexerListAdapter(Context context) {
- super(context);
- mContext = context;
- }
-
- /**
- * Creates a section header view that will be pinned at the top of the list
- * as the user scrolls.
- */
- protected abstract View createPinnedSectionHeaderView(Context context, ViewGroup parent);
-
- /**
- * Sets the title in the pinned header as the user scrolls.
- */
- 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;
- }
-
- public void setSectionHeaderDisplayEnabled(boolean flag) {
- this.mSectionHeaderDisplayEnabled = flag;
- }
-
- public int getIndexedPartition() {
- return mIndexedPartition;
- }
-
- public void setIndexedPartition(int partition) {
- this.mIndexedPartition = partition;
- }
-
- public SectionIndexer getIndexer() {
- return mIndexer;
- }
-
- public void setIndexer(SectionIndexer indexer) {
- mIndexer = indexer;
- mPlacementCache.invalidate();
- }
-
- public Object[] getSections() {
- if (mIndexer == null) {
- return new String[] { " " };
- } else {
- return mIndexer.getSections();
- }
- }
-
- /**
- * @return relative position of the section in the indexed partition
- */
- public int getPositionForSection(int sectionIndex) {
- if (mIndexer == null) {
- return -1;
- }
-
- return mIndexer.getPositionForSection(sectionIndex);
- }
-
- /**
- * @param position relative position in the indexed partition
- */
- public int getSectionForPosition(int position) {
- if (mIndexer == null) {
- return -1;
- }
-
- return mIndexer.getSectionForPosition(position);
- }
-
- @Override
- public int getPinnedHeaderCount() {
- if (isSectionHeaderDisplayEnabled()) {
- return super.getPinnedHeaderCount() + 1;
- } else {
- return super.getPinnedHeaderCount();
- }
- }
-
- @Override
- public View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent) {
- if (isSectionHeaderDisplayEnabled() && viewIndex == getPinnedHeaderCount() - 1) {
- if (mHeader == null) {
- mHeader = createPinnedSectionHeaderView(mContext, parent);
- }
- return mHeader;
- } else {
- return super.getPinnedHeaderView(viewIndex, convertView, parent);
- }
- }
-
- @Override
- public void configurePinnedHeaders(PinnedHeaderListView listView) {
- super.configurePinnedHeaders(listView);
-
- if (!isSectionHeaderDisplayEnabled()) {
- return;
- }
-
- int index = getPinnedHeaderCount() - 1;
- if (mIndexer == null || getCount() == 0) {
- listView.setHeaderInvisible(index, false);
- } else {
- int listPosition = listView.getPositionAt(listView.getTotalTopPinnedHeaderHeight());
- int position = listPosition - listView.getHeaderViewsCount();
-
- int section = -1;
- int partition = getPartitionForPosition(position);
- if (partition == mIndexedPartition) {
- int offset = getOffsetInPartition(position);
- if (offset != -1) {
- section = getSectionForPosition(offset);
- }
- }
-
- if (section == -1) {
- 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)) {
- partitionStart++;
- }
-
- // Compute the item position where the next section begins
- int nextSectionPosition = partitionStart + getPositionForSection(section + 1);
- boolean isLastInSection = position == nextSectionPosition - 1;
- listView.setFadingHeader(index, listPosition, isLastInSection);
- }
- }
- }
-
- /**
- * Computes the item's placement within its section and populates the {@code placement}
- * object accordingly. Please note that the returned object is volatile and should be
- * copied if the result needs to be used later.
- */
- public Placement getItemPlacementInSection(int position) {
- if (mPlacementCache.position == position) {
- return mPlacementCache;
- }
-
- mPlacementCache.position = position;
- if (isSectionHeaderDisplayEnabled()) {
- int section = getSectionForPosition(position);
- if (section != -1 && getPositionForSection(section) == position) {
- mPlacementCache.firstInSection = true;
- mPlacementCache.sectionHeader = (String)getSections()[section];
- } else {
- mPlacementCache.firstInSection = false;
- mPlacementCache.sectionHeader = null;
- }
-
- mPlacementCache.lastInSection = (getPositionForSection(section + 1) - 1 == position);
- } else {
- mPlacementCache.firstInSection = false;
- mPlacementCache.lastInSection = false;
- mPlacementCache.sectionHeader = null;
- }
- return mPlacementCache;
- }
-}
diff --git a/src/com/android/contacts/widget/PinnedHeaderListAdapter.java b/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
deleted file mode 100644
index c01e331..0000000
--- a/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2010 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.widget;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.common.widget.CompositeCursorAdapter;
-
-/**
- * A subclass of {@link CompositeCursorAdapter} that manages pinned partition headers.
- */
-public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter
- implements PinnedHeaderListView.PinnedHeaderAdapter {
-
- public static final int PARTITION_HEADER_TYPE = 0;
-
- private boolean mPinnedPartitionHeadersEnabled;
- private boolean mHeaderVisibility[];
-
- public PinnedHeaderListAdapter(Context context) {
- super(context);
- }
-
- public PinnedHeaderListAdapter(Context context, int initialCapacity) {
- super(context, initialCapacity);
- }
-
- public boolean getPinnedPartitionHeadersEnabled() {
- return mPinnedPartitionHeadersEnabled;
- }
-
- public void setPinnedPartitionHeadersEnabled(boolean flag) {
- this.mPinnedPartitionHeadersEnabled = flag;
- }
-
- @Override
- public int getPinnedHeaderCount() {
- if (mPinnedPartitionHeadersEnabled) {
- return getPartitionCount();
- } else {
- return 0;
- }
- }
-
- protected boolean isPinnedPartitionHeaderVisible(int partition) {
- return mPinnedPartitionHeadersEnabled && hasHeader(partition)
- && !isPartitionEmpty(partition);
- }
-
- /**
- * The default implementation creates the same type of view as a normal
- * partition header.
- */
- @Override
- public View getPinnedHeaderView(int partition, View convertView, ViewGroup parent) {
- if (hasHeader(partition)) {
- View view = null;
- if (convertView != null) {
- Integer headerType = (Integer)convertView.getTag();
- if (headerType != null && headerType == PARTITION_HEADER_TYPE) {
- view = convertView;
- }
- }
- if (view == null) {
- view = newHeaderView(getContext(), partition, null, parent);
- view.setTag(PARTITION_HEADER_TYPE);
- view.setFocusable(false);
- view.setEnabled(false);
- }
- bindHeaderView(view, partition, getCursor(partition));
- return view;
- } else {
- return null;
- }
- }
-
- @Override
- public void configurePinnedHeaders(PinnedHeaderListView listView) {
- if (!mPinnedPartitionHeadersEnabled) {
- return;
- }
-
- int size = getPartitionCount();
-
- // Cache visibility bits, because we will need them several times later on
- if (mHeaderVisibility == null || mHeaderVisibility.length != size) {
- mHeaderVisibility = new boolean[size];
- }
- for (int i = 0; i < size; i++) {
- boolean visible = isPinnedPartitionHeaderVisible(i);
- mHeaderVisibility[i] = visible;
- if (!visible) {
- listView.setHeaderInvisible(i, true);
- }
- }
-
- int headerViewsCount = listView.getHeaderViewsCount();
-
- // Starting at the top, find and pin headers for partitions preceding the visible one(s)
- int maxTopHeader = -1;
- int topHeaderHeight = 0;
- for (int i = 0; i < size; i++) {
- if (mHeaderVisibility[i]) {
- int position = listView.getPositionAt(topHeaderHeight) - headerViewsCount;
- int partition = getPartitionForPosition(position);
- if (i > partition) {
- break;
- }
-
- listView.setHeaderPinnedAtTop(i, topHeaderHeight, false);
- topHeaderHeight += listView.getPinnedHeaderHeight(i);
- maxTopHeader = i;
- }
- }
-
- // Starting at the bottom, find and pin headers for partitions following the visible one(s)
- int maxBottomHeader = size;
- int bottomHeaderHeight = 0;
- int listHeight = listView.getHeight();
- for (int i = size; --i > maxTopHeader;) {
- if (mHeaderVisibility[i]) {
- int position = listView.getPositionAt(listHeight - bottomHeaderHeight)
- - headerViewsCount;
- if (position < 0) {
- break;
- }
-
- int partition = getPartitionForPosition(position - 1);
- if (partition == -1 || i <= partition) {
- break;
- }
-
- int height = listView.getPinnedHeaderHeight(i);
- bottomHeaderHeight += height;
- // Animate the header only if the partition is completely invisible below
- // the bottom of the view
- int firstPositionForPartition = getPositionForPartition(i);
- boolean animate = position < firstPositionForPartition;
- listView.setHeaderPinnedAtBottom(i, listHeight - bottomHeaderHeight, animate);
- maxBottomHeader = i;
- }
- }
-
- // Headers in between the top-pinned and bottom-pinned should be hidden
- for (int i = maxTopHeader + 1; i < maxBottomHeader; i++) {
- if (mHeaderVisibility[i]) {
- listView.setHeaderInvisible(i, isPartitionEmpty(i));
- }
- }
- }
-
- @Override
- public int getScrollPositionForHeader(int viewIndex) {
- return getPositionForPartition(viewIndex);
- }
-}
diff --git a/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java b/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java
index 4276791..027cc9d 100644
--- a/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java
+++ b/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java
@@ -28,9 +28,10 @@
import android.widget.TextView;
import com.android.contacts.R;
+import com.android.contacts.common.list.PinnedHeaderListAdapter;
/**
- * An activity that demonstrates various use cases for the {@link PinnedHeaderListView}.
+ * An activity that demonstrates various use cases for the {@link com.android.contacts.common.list.PinnedHeaderListView}.
* If we decide to move PinnedHeaderListView to the framework, this class could go
* to API demos.
*/
diff --git a/src/com/android/contacts/widget/PinnedHeaderListView.java b/src/com/android/contacts/widget/PinnedHeaderListView.java
deleted file mode 100644
index 1503879..0000000
--- a/src/com/android/contacts/widget/PinnedHeaderListView.java
+++ /dev/null
@@ -1,522 +0,0 @@
-/*
- * Copyright (C) 2010 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.widget;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ListAdapter;
-
-/**
- * A ListView that maintains a header pinned at the top of the list. The
- * pinned header can be pushed up and dissolved as needed.
- */
-public class PinnedHeaderListView extends AutoScrollListView
- implements OnScrollListener, OnItemSelectedListener {
-
- /**
- * Adapter interface. The list adapter must implement this interface.
- */
- public interface PinnedHeaderAdapter {
-
- /**
- * Returns the overall number of pinned headers, visible or not.
- */
- int getPinnedHeaderCount();
-
- /**
- * Creates or updates the pinned header view.
- */
- View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent);
-
- /**
- * Configures the pinned headers to match the visible list items. The
- * adapter should call {@link PinnedHeaderListView#setHeaderPinnedAtTop},
- * {@link PinnedHeaderListView#setHeaderPinnedAtBottom},
- * {@link PinnedHeaderListView#setFadingHeader} or
- * {@link PinnedHeaderListView#setHeaderInvisible}, for each header that
- * needs to change its position or visibility.
- */
- void configurePinnedHeaders(PinnedHeaderListView listView);
-
- /**
- * Returns the list position to scroll to if the pinned header is touched.
- * Return -1 if the list does not need to be scrolled.
- */
- int getScrollPositionForHeader(int viewIndex);
- }
-
- private static final int MAX_ALPHA = 255;
- private static final int TOP = 0;
- private static final int BOTTOM = 1;
- private static final int FADING = 2;
-
- private static final int DEFAULT_ANIMATION_DURATION = 20;
-
- private static final class PinnedHeader {
- View view;
- boolean visible;
- int y;
- int height;
- int alpha;
- int state;
-
- boolean animating;
- boolean targetVisible;
- int sourceY;
- int targetY;
- long targetTime;
- }
-
- private PinnedHeaderAdapter mAdapter;
- private int mSize;
- private PinnedHeader[] mHeaders;
- private RectF mBounds = new RectF();
- private Rect mClipRect = new Rect();
- private OnScrollListener mOnScrollListener;
- private OnItemSelectedListener mOnItemSelectedListener;
- private int mScrollState;
-
- private int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
- private boolean mAnimating;
- private long mAnimationTargetTime;
- private int mHeaderPaddingLeft;
- private int mHeaderWidth;
-
- public PinnedHeaderListView(Context context) {
- this(context, null, com.android.internal.R.attr.listViewStyle);
- }
-
- public PinnedHeaderListView(Context context, AttributeSet attrs) {
- this(context, attrs, com.android.internal.R.attr.listViewStyle);
- }
-
- public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- super.setOnScrollListener(this);
- super.setOnItemSelectedListener(this);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- mHeaderPaddingLeft = getPaddingLeft();
- mHeaderWidth = r - l - mHeaderPaddingLeft - getPaddingRight();
- }
-
- public void setPinnedHeaderAnimationDuration(int duration) {
- mAnimationDuration = duration;
- }
-
- @Override
- public void setAdapter(ListAdapter adapter) {
- mAdapter = (PinnedHeaderAdapter)adapter;
- super.setAdapter(adapter);
- }
-
- @Override
- public void setOnScrollListener(OnScrollListener onScrollListener) {
- mOnScrollListener = onScrollListener;
- super.setOnScrollListener(this);
- }
-
- @Override
- public void setOnItemSelectedListener(OnItemSelectedListener listener) {
- mOnItemSelectedListener = listener;
- super.setOnItemSelectedListener(this);
- }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
- if (mAdapter != null) {
- int count = mAdapter.getPinnedHeaderCount();
- if (count != mSize) {
- mSize = count;
- if (mHeaders == null) {
- mHeaders = new PinnedHeader[mSize];
- } else if (mHeaders.length < mSize) {
- PinnedHeader[] headers = mHeaders;
- mHeaders = new PinnedHeader[mSize];
- System.arraycopy(headers, 0, mHeaders, 0, headers.length);
- }
- }
-
- for (int i = 0; i < mSize; i++) {
- if (mHeaders[i] == null) {
- mHeaders[i] = new PinnedHeader();
- }
- mHeaders[i].view = mAdapter.getPinnedHeaderView(i, mHeaders[i].view, this);
- }
-
- mAnimationTargetTime = System.currentTimeMillis() + mAnimationDuration;
- mAdapter.configurePinnedHeaders(this);
- invalidateIfAnimating();
-
- }
- if (mOnScrollListener != null) {
- mOnScrollListener.onScroll(this, firstVisibleItem, visibleItemCount, totalItemCount);
- }
- }
-
- @Override
- protected float getTopFadingEdgeStrength() {
- // Disable vertical fading at the top when the pinned header is present
- return mSize > 0 ? 0 : super.getTopFadingEdgeStrength();
- }
-
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- mScrollState = scrollState;
- if (mOnScrollListener != null) {
- mOnScrollListener.onScrollStateChanged(this, scrollState);
- }
- }
-
- /**
- * Ensures that the selected item is positioned below the top-pinned headers
- * and above the bottom-pinned ones.
- */
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- int height = getHeight();
-
- int windowTop = 0;
- int windowBottom = height;
-
- for (int i = 0; i < mSize; i++) {
- PinnedHeader header = mHeaders[i];
- if (header.visible) {
- if (header.state == TOP) {
- windowTop = header.y + header.height;
- } else if (header.state == BOTTOM) {
- windowBottom = header.y;
- break;
- }
- }
- }
-
- View selectedView = getSelectedView();
- if (selectedView != null) {
- if (selectedView.getTop() < windowTop) {
- setSelectionFromTop(position, windowTop);
- } else if (selectedView.getBottom() > windowBottom) {
- setSelectionFromTop(position, windowBottom - selectedView.getHeight());
- }
- }
-
- if (mOnItemSelectedListener != null) {
- mOnItemSelectedListener.onItemSelected(parent, view, position, id);
- }
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- if (mOnItemSelectedListener != null) {
- mOnItemSelectedListener.onNothingSelected(parent);
- }
- }
-
- public int getPinnedHeaderHeight(int viewIndex) {
- ensurePinnedHeaderLayout(viewIndex);
- return mHeaders[viewIndex].view.getHeight();
- }
-
- /**
- * Set header to be pinned at the top.
- *
- * @param viewIndex index of the header view
- * @param y is position of the header in pixels.
- * @param animate true if the transition to the new coordinate should be animated
- */
- public void setHeaderPinnedAtTop(int viewIndex, int y, boolean animate) {
- ensurePinnedHeaderLayout(viewIndex);
- PinnedHeader header = mHeaders[viewIndex];
- header.visible = true;
- header.y = y;
- header.state = TOP;
-
- // TODO perhaps we should animate at the top as well
- header.animating = false;
- }
-
- /**
- * Set header to be pinned at the bottom.
- *
- * @param viewIndex index of the header view
- * @param y is position of the header in pixels.
- * @param animate true if the transition to the new coordinate should be animated
- */
- public void setHeaderPinnedAtBottom(int viewIndex, int y, boolean animate) {
- ensurePinnedHeaderLayout(viewIndex);
- PinnedHeader header = mHeaders[viewIndex];
- header.state = BOTTOM;
- if (header.animating) {
- header.targetTime = mAnimationTargetTime;
- header.sourceY = header.y;
- header.targetY = y;
- } else if (animate && (header.y != y || !header.visible)) {
- if (header.visible) {
- header.sourceY = header.y;
- } else {
- header.visible = true;
- header.sourceY = y + header.height;
- }
- header.animating = true;
- header.targetVisible = true;
- header.targetTime = mAnimationTargetTime;
- header.targetY = y;
- } else {
- header.visible = true;
- header.y = y;
- }
- }
-
- /**
- * Set header to be pinned at the top of the first visible item.
- *
- * @param viewIndex index of the header view
- * @param position is position of the header in pixels.
- */
- public void setFadingHeader(int viewIndex, int position, boolean fade) {
- ensurePinnedHeaderLayout(viewIndex);
-
- View child = getChildAt(position - getFirstVisiblePosition());
- if (child == null) return;
-
- PinnedHeader header = mHeaders[viewIndex];
- header.visible = true;
- header.state = FADING;
- header.alpha = MAX_ALPHA;
- header.animating = false;
-
- int top = getTotalTopPinnedHeaderHeight();
- header.y = top;
- if (fade) {
- int bottom = child.getBottom() - top;
- int headerHeight = header.height;
- if (bottom < headerHeight) {
- int portion = bottom - headerHeight;
- header.alpha = MAX_ALPHA * (headerHeight + portion) / headerHeight;
- header.y = top + portion;
- }
- }
- }
-
- /**
- * Makes header invisible.
- *
- * @param viewIndex index of the header view
- * @param animate true if the transition to the new coordinate should be animated
- */
- public void setHeaderInvisible(int viewIndex, boolean animate) {
- PinnedHeader header = mHeaders[viewIndex];
- if (header.visible && (animate || header.animating) && header.state == BOTTOM) {
- header.sourceY = header.y;
- if (!header.animating) {
- header.visible = true;
- header.targetY = getBottom() + header.height;
- }
- header.animating = true;
- header.targetTime = mAnimationTargetTime;
- header.targetVisible = false;
- } else {
- header.visible = false;
- }
- }
-
- private void ensurePinnedHeaderLayout(int viewIndex) {
- View view = mHeaders[viewIndex].view;
- if (view.isLayoutRequested()) {
- int widthSpec = MeasureSpec.makeMeasureSpec(mHeaderWidth, MeasureSpec.EXACTLY);
- int heightSpec;
- ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
- if (layoutParams != null && layoutParams.height > 0) {
- heightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
- } else {
- heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- }
- view.measure(widthSpec, heightSpec);
- int height = view.getMeasuredHeight();
- mHeaders[viewIndex].height = height;
- view.layout(0, 0, mHeaderWidth, height);
- }
- }
-
- /**
- * Returns the sum of heights of headers pinned to the top.
- */
- public int getTotalTopPinnedHeaderHeight() {
- for (int i = mSize; --i >= 0;) {
- PinnedHeader header = mHeaders[i];
- if (header.visible && header.state == TOP) {
- return header.y + header.height;
- }
- }
- return 0;
- }
-
- /**
- * Returns the list item position at the specified y coordinate.
- */
- public int getPositionAt(int y) {
- do {
- int position = pointToPosition(getPaddingLeft() + 1, y);
- if (position != -1) {
- return position;
- }
- // If position == -1, we must have hit a separator. Let's examine
- // a nearby pixel
- y--;
- } while (y > 0);
- return 0;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (mScrollState == SCROLL_STATE_IDLE) {
- final int y = (int)ev.getY();
- for (int i = mSize; --i >= 0;) {
- PinnedHeader header = mHeaders[i];
- if (header.visible && header.y <= y && header.y + header.height > y) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- return smoothScrollToPartition(i);
- } else {
- return true;
- }
- }
- }
- }
-
- return super.onInterceptTouchEvent(ev);
- }
-
- private boolean smoothScrollToPartition(int partition) {
- final int position = mAdapter.getScrollPositionForHeader(partition);
- if (position == -1) {
- return false;
- }
-
- int offset = 0;
- for (int i = 0; i < partition; i++) {
- PinnedHeader header = mHeaders[i];
- if (header.visible) {
- offset += header.height;
- }
- }
-
- smoothScrollToPositionFromTop(position + getHeaderViewsCount(), offset);
- return true;
- }
-
- private void invalidateIfAnimating() {
- mAnimating = false;
- for (int i = 0; i < mSize; i++) {
- if (mHeaders[i].animating) {
- mAnimating = true;
- invalidate();
- return;
- }
- }
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- long currentTime = mAnimating ? System.currentTimeMillis() : 0;
-
- int top = 0;
- int bottom = getBottom();
- boolean hasVisibleHeaders = false;
- for (int i = 0; i < mSize; i++) {
- PinnedHeader header = mHeaders[i];
- if (header.visible) {
- hasVisibleHeaders = true;
- if (header.state == BOTTOM && header.y < bottom) {
- bottom = header.y;
- } else if (header.state == TOP || header.state == FADING) {
- int newTop = header.y + header.height;
- if (newTop > top) {
- top = newTop;
- }
- }
- }
- }
-
- if (hasVisibleHeaders) {
- canvas.save();
- mClipRect.set(0, top, getWidth(), bottom);
- canvas.clipRect(mClipRect);
- }
-
- super.dispatchDraw(canvas);
-
- if (hasVisibleHeaders) {
- canvas.restore();
-
- // First draw top headers, then the bottom ones to handle the Z axis correctly
- for (int i = mSize; --i >= 0;) {
- PinnedHeader header = mHeaders[i];
- if (header.visible && (header.state == TOP || header.state == FADING)) {
- drawHeader(canvas, header, currentTime);
- }
- }
-
- for (int i = 0; i < mSize; i++) {
- PinnedHeader header = mHeaders[i];
- if (header.visible && header.state == BOTTOM) {
- drawHeader(canvas, header, currentTime);
- }
- }
- }
-
- invalidateIfAnimating();
- }
-
- private void drawHeader(Canvas canvas, PinnedHeader header, long currentTime) {
- if (header.animating) {
- int timeLeft = (int)(header.targetTime - currentTime);
- if (timeLeft <= 0) {
- header.y = header.targetY;
- header.visible = header.targetVisible;
- header.animating = false;
- } else {
- header.y = header.targetY + (header.sourceY - header.targetY) * timeLeft
- / mAnimationDuration;
- }
- }
- if (header.visible) {
- View view = header.view;
- int saveCount = canvas.save();
- canvas.translate(mHeaderPaddingLeft, header.y);
- if (header.state == FADING) {
- mBounds.set(0, 0, mHeaderWidth, view.getHeight());
- canvas.saveLayerAlpha(mBounds, header.alpha, Canvas.ALL_SAVE_FLAG);
- }
- view.draw(canvas);
- canvas.restoreToCount(saveCount);
- }
- }
-}