| /* |
| * 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.common.list; |
| |
| import android.content.Context; |
| import android.content.CursorLoader; |
| import android.content.res.Resources; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.ContactsContract; |
| import android.provider.ContactsContract.CommonDataKinds.Phone; |
| import android.provider.ContactsContract.Contacts; |
| import android.provider.ContactsContract.Directory; |
| import android.text.TextUtils; |
| 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.ContactsUtils; |
| import com.android.contacts.common.R; |
| import com.android.contacts.common.util.SearchUtil; |
| import com.android.dialer.common.LogUtil; |
| import com.android.dialer.common.cp2.DirectoryCompat; |
| import com.android.dialer.compat.CompatUtils; |
| import com.android.dialer.configprovider.ConfigProviderBindings; |
| import com.android.dialer.contactphoto.ContactPhotoManager; |
| import com.android.dialer.contactphoto.ContactPhotoManager.DefaultImageRequest; |
| import com.android.dialer.logging.InteractionEvent; |
| import com.android.dialer.logging.Logger; |
| 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 { |
| |
| /** |
| * Indicates whether the {@link Directory#LOCAL_INVISIBLE} directory should be included in the |
| * search. |
| */ |
| public static final boolean LOCAL_INVISIBLE_DIRECTORY_ENABLED = false; |
| |
| private int mDisplayOrder; |
| private int mSortOrder; |
| |
| private boolean mDisplayPhotos; |
| private boolean mCircularPhotos = true; |
| private boolean mQuickContactEnabled; |
| private boolean mAdjustSelectionBoundsEnabled; |
| |
| /** The root view of the fragment that this adapter is associated with. */ |
| private View mFragmentRootView; |
| |
| private ContactPhotoManager mPhotoLoader; |
| |
| private String mQueryString; |
| private String mUpperCaseQueryString; |
| private boolean mSearchMode; |
| private int mDirectorySearchMode; |
| private int mDirectoryResultLimit = Integer.MAX_VALUE; |
| |
| private boolean mEmptyListEnabled = true; |
| |
| private boolean mSelectionVisible; |
| |
| private ContactListFilter mFilter; |
| private boolean mDarkTheme = false; |
| |
| public static final int SUGGESTIONS_LOADER_ID = 0; |
| |
| /** Resource used to provide header-text for default filter. */ |
| private CharSequence mDefaultFilterHeaderText; |
| |
| public ContactEntryListAdapter(Context context) { |
| super(context); |
| setDefaultFilterHeaderText(R.string.local_search_label); |
| addPartitions(); |
| } |
| |
| /** |
| * @param fragmentRootView Root view of the fragment. This is used to restrict the scope of image |
| * loading requests that get cancelled on cursor changes. |
| */ |
| protected void setFragmentRootView(View fragmentRootView) { |
| mFragmentRootView = fragmentRootView; |
| } |
| |
| protected void setDefaultFilterHeaderText(int resourceId) { |
| mDefaultFilterHeaderText = getContext().getResources().getText(resourceId); |
| } |
| |
| @Override |
| protected ContactListItemView newView( |
| Context context, int partition, Cursor cursor, int position, ViewGroup parent) { |
| final ContactListItemView view = new ContactListItemView(context, null); |
| view.setIsSectionHeaderEnabled(isSectionHeaderDisplayEnabled()); |
| view.setAdjustSelectionBoundsEnabled(isAdjustSelectionBoundsEnabled()); |
| return view; |
| } |
| |
| @Override |
| protected void bindView(View itemView, int partition, Cursor cursor, int position) { |
| final ContactListItemView view = (ContactListItemView) itemView; |
| view.setIsSectionHeaderEnabled(isSectionHeaderDisplayEnabled()); |
| bindWorkProfileIcon(view, partition); |
| } |
| |
| @Override |
| protected View createPinnedSectionHeaderView(Context context, ViewGroup parent) { |
| return new ContactListPinnedHeaderView(context, null, parent); |
| } |
| |
| @Override |
| protected void setPinnedSectionTitle(View pinnedHeaderView, String title) { |
| ((ContactListPinnedHeaderView) pinnedHeaderView).setSectionHeaderTitle(title); |
| } |
| |
| protected void addPartitions() { |
| if (ConfigProviderBindings.get(getContext()).getBoolean("p13n_ranker_should_enable", false)) { |
| addPartition(createSuggestionsDirectoryPartition()); |
| } |
| addPartition(createDefaultDirectoryPartition()); |
| } |
| |
| protected DirectoryPartition createSuggestionsDirectoryPartition() { |
| DirectoryPartition partition = new DirectoryPartition(true, true); |
| partition.setDirectoryId(SUGGESTIONS_LOADER_ID); |
| partition.setDirectoryType(getContext().getString(R.string.contact_suggestions)); |
| partition.setPriorityDirectory(true); |
| partition.setPhotoSupported(true); |
| partition.setLabel(getContext().getString(R.string.local_suggestions_search_label)); |
| return partition; |
| } |
| |
| 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); |
| partition.setLabel(mDefaultFilterHeaderText.toString()); |
| 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. |
| * |
| * <p>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). |
| */ |
| public 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); |
| } |
| } |
| } |
| |
| protected 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; |
| } |
| |
| protected DirectoryPartition getDirectoryById(long id) { |
| int count = getPartitionCount(); |
| for (int i = 0; i < count; i++) { |
| Partition partition = getPartition(i); |
| if (partition instanceof DirectoryPartition) { |
| final DirectoryPartition directoryPartition = (DirectoryPartition) partition; |
| if (directoryPartition.getDirectoryId() == id) { |
| return directoryPartition; |
| } |
| } |
| } |
| return null; |
| } |
| |
| 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 = SearchUtil.cleanStartAndEndOfSearchQuery(queryString.toUpperCase()); |
| } |
| |
| // Enable default partition header if in search mode (including zero-suggest). |
| if (mQueryString != null) { |
| setDefaultPartitionHeader(true); |
| } |
| } |
| |
| public String 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 getDirectoryResultLimit(DirectoryPartition directoryPartition) { |
| final int limit = directoryPartition.getResultLimit(); |
| return limit == DirectoryPartition.RESULT_LIMIT_DEFAULT ? 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; |
| } |
| |
| protected ContactPhotoManager getPhotoLoader() { |
| return mPhotoLoader; |
| } |
| |
| public void setPhotoLoader(ContactPhotoManager photoLoader) { |
| mPhotoLoader = photoLoader; |
| } |
| |
| public boolean getDisplayPhotos() { |
| return mDisplayPhotos; |
| } |
| |
| public void setDisplayPhotos(boolean displayPhotos) { |
| mDisplayPhotos = displayPhotos; |
| } |
| |
| public boolean getCircularPhotos() { |
| return mCircularPhotos; |
| } |
| |
| 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 isAdjustSelectionBoundsEnabled() { |
| return mAdjustSelectionBoundsEnabled; |
| } |
| |
| public void setAdjustSelectionBoundsEnabled(boolean enabled) { |
| mAdjustSelectionBoundsEnabled = enabled; |
| } |
| |
| public void setProfileExists(boolean exists) { |
| // Stick the "ME" header for the profile |
| if (exists) { |
| setSectionHeader(R.string.user_profile_contacts_list_header, /* # of ME */ 1); |
| } |
| } |
| |
| private void setSectionHeader(int resId, int numberOfItems) { |
| SectionIndexer indexer = getIndexer(); |
| if (indexer != null) { |
| ((ContactsSectionIndexer) indexer) |
| .setProfileAndFavoritesHeader(getContext().getString(resId), numberOfItems); |
| } |
| } |
| |
| public void setDarkTheme(boolean value) { |
| mDarkTheme = value; |
| } |
| |
| /** 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. |
| LogUtil.i( |
| "ContactEntryListAdapter.changeDirectories", |
| "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); |
| if (DirectoryCompat.isRemoteDirectoryId(id)) { |
| if (DirectoryCompat.isEnterpriseDirectoryId(id)) { |
| partition.setLabel(mContext.getString(R.string.directory_search_label_work)); |
| } else { |
| partition.setLabel(mContext.getString(R.string.directory_search_label)); |
| } |
| } else { |
| if (DirectoryCompat.isEnterpriseDirectoryId(id)) { |
| partition.setLabel(mContext.getString(R.string.list_filter_phones_work)); |
| } else { |
| partition.setLabel(mDefaultFilterHeaderText.toString()); |
| } |
| } |
| 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); |
| } |
| |
| // When the cursor changes, cancel any pending asynchronous photo loads. |
| mPhotoLoader.cancelPendingRequests(mFragmentRootView); |
| } |
| |
| 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 || cursor.isClosed()) { |
| setIndexer(null); |
| return; |
| } |
| |
| Bundle bundle = cursor.getExtras(); |
| if (bundle.containsKey(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES) |
| && bundle.containsKey(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS)) { |
| String[] sections = bundle.getStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES); |
| int[] counts = bundle.getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS); |
| |
| if (getExtraStartingSection()) { |
| // Insert an additional unnamed section at the top of the list. |
| String[] allSections = new String[sections.length + 1]; |
| int[] allCounts = new int[counts.length + 1]; |
| for (int i = 0; i < sections.length; i++) { |
| allSections[i + 1] = sections[i]; |
| allCounts[i + 1] = counts[i]; |
| } |
| allCounts[0] = 1; |
| allSections[0] = ""; |
| setIndexer(new ContactsSectionIndexer(allSections, allCounts)); |
| } else { |
| setIndexer(new ContactsSectionIndexer(sections, counts)); |
| } |
| } else { |
| setIndexer(null); |
| } |
| } |
| |
| protected boolean getExtraStartingSection() { |
| return false; |
| } |
| |
| @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 { |
| 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; |
| } |
| |
| /** Configures visibility parameters for the directory partitions. */ |
| public void configurePartitionsVisibility(boolean isInSearchMode) { |
| for (int i = 0; i < getPartitionCount(); i++) { |
| setShowIfEmpty(i, false); |
| setHasHeader(i, isInSearchMode); |
| } |
| } |
| |
| // Sets header for the default partition. |
| private void setDefaultPartitionHeader(boolean setHeader) { |
| // Iterate in reverse here to ensure the first DEFAULT directory has header. |
| // Both "Suggestions" and "All Contacts" directories have DEFAULT id. |
| int defaultPartitionIndex = -1; |
| for (int i = getPartitionCount() - 1; i >= 0; i--) { |
| Partition partition = getPartition(i); |
| if (partition instanceof DirectoryPartition |
| && ((DirectoryPartition) partition).getDirectoryId() == Directory.DEFAULT) { |
| defaultPartitionIndex = i; |
| } |
| } |
| setHasHeader(defaultPartitionIndex, setHeader); |
| } |
| |
| @Override |
| protected View newHeaderView(Context context, int partition, Cursor cursor, ViewGroup parent) { |
| LayoutInflater inflater = LayoutInflater.from(context); |
| View view = inflater.inflate(R.layout.directory_header, parent, false); |
| if (!getPinnedPartitionHeadersEnabled()) { |
| // If the headers are unpinned, there is no need for their background |
| // color to be non-transparent. Setting this transparent reduces maintenance for |
| // non-pinned headers. We don't need to bother synchronizing the activity's |
| // background color with the header background color. |
| view.setBackground(null); |
| } |
| return view; |
| } |
| |
| protected void bindWorkProfileIcon(final ContactListItemView view, int partitionId) { |
| final Partition partition = getPartition(partitionId); |
| if (partition instanceof DirectoryPartition) { |
| final DirectoryPartition directoryPartition = (DirectoryPartition) partition; |
| final long directoryId = directoryPartition.getDirectoryId(); |
| final long userType = ContactsUtils.determineUserType(directoryId, null); |
| view.setWorkProfileIconEnabled(userType == ContactsUtils.USER_TYPE_WORK); |
| } |
| } |
| |
| @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); |
| labelTextView.setText(directoryPartition.getLabel()); |
| if (!DirectoryCompat.isRemoteDirectoryId(directoryId)) { |
| displayNameTextView.setText(null); |
| } else { |
| String directoryName = directoryPartition.getDisplayName(); |
| String displayName = |
| !TextUtils.isEmpty(directoryName) ? directoryName : directoryPartition.getDirectoryType(); |
| displayNameTextView.setText(displayName); |
| } |
| |
| final Resources res = getContext().getResources(); |
| final int headerPaddingTop = |
| partitionIndex == 1 && getPartition(0).isEmpty() |
| ? 0 |
| : res.getDimensionPixelOffset(R.dimen.directory_header_extra_top_padding); |
| // There should be no extra padding at the top of the first directory header |
| view.setPaddingRelative( |
| view.getPaddingStart(), headerPaddingTop, view.getPaddingEnd(), view.getPaddingBottom()); |
| } |
| |
| /** 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; |
| } |
| |
| 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 |
| * @param displayNameColumn Index of the display name column |
| */ |
| protected void bindQuickContact( |
| final ContactListItemView view, |
| int partitionIndex, |
| Cursor cursor, |
| int photoIdColumn, |
| int photoUriColumn, |
| int contactIdColumn, |
| int lookUpKeyColumn, |
| int displayNameColumn) { |
| long photoId = 0; |
| if (!cursor.isNull(photoIdColumn)) { |
| photoId = cursor.getLong(photoIdColumn); |
| } |
| |
| QuickContactBadge quickContact = view.getQuickContact(); |
| quickContact.assignContactUri( |
| getContactUri(partitionIndex, cursor, contactIdColumn, lookUpKeyColumn)); |
| if (CompatUtils.hasPrioritizedMimeType()) { |
| // The Contacts app never uses the QuickContactBadge. Therefore, it is safe to assume |
| // that only Dialer will use this QuickContact badge. This means prioritizing the phone |
| // mimetype here is reasonable. |
| quickContact.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE); |
| } |
| Logger.get(mContext) |
| .logQuickContactOnTouch( |
| quickContact, InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_SEARCH, true); |
| |
| if (photoId != 0 || photoUriColumn == -1) { |
| getPhotoLoader().loadThumbnail(quickContact, photoId, mDarkTheme, mCircularPhotos, null); |
| } else { |
| final String photoUriString = cursor.getString(photoUriColumn); |
| final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString); |
| DefaultImageRequest request = null; |
| if (photoUri == null) { |
| request = getDefaultImageRequestFromCursor(cursor, displayNameColumn, lookUpKeyColumn); |
| } |
| getPhotoLoader().loadPhoto(quickContact, photoUri, -1, mDarkTheme, mCircularPhotos, request); |
| } |
| } |
| |
| @Override |
| public boolean hasStableIds() { |
| // Whenever bindViewId() is called, the values passed into setId() are stable or |
| // stable-ish. For example, when one contact is modified we don't expect a second |
| // contact's Contact._ID values to change. |
| return true; |
| } |
| |
| protected void bindViewId(final ContactListItemView view, Cursor cursor, int idColumn) { |
| // Set a semi-stable id, so that talkback won't get confused when the list gets |
| // refreshed. There is little harm in inserting the same ID twice. |
| long contactId = cursor.getLong(idColumn); |
| view.setId((int) (contactId % Integer.MAX_VALUE)); |
| } |
| |
| protected Uri getContactUri( |
| int partitionIndex, Cursor cursor, int contactIdColumn, int lookUpKeyColumn) { |
| long contactId = cursor.getLong(contactIdColumn); |
| String lookupKey = cursor.getString(lookUpKeyColumn); |
| long directoryId = ((DirectoryPartition) getPartition(partitionIndex)).getDirectoryId(); |
| Uri uri = Contacts.getLookupUri(contactId, lookupKey); |
| if (uri != null && directoryId != Directory.DEFAULT) { |
| uri = |
| uri.buildUpon() |
| .appendQueryParameter( |
| ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)) |
| .build(); |
| } |
| return uri; |
| } |
| |
| /** |
| * Retrieves the lookup key and display name from a cursor, and returns a {@link |
| * DefaultImageRequest} containing these contact details |
| * |
| * @param cursor Contacts cursor positioned at the current row to retrieve contact details for |
| * @param displayNameColumn Column index of the display name |
| * @param lookupKeyColumn Column index of the lookup key |
| * @return {@link DefaultImageRequest} with the displayName and identifier fields set to the |
| * display name and lookup key of the contact. |
| */ |
| public DefaultImageRequest getDefaultImageRequestFromCursor( |
| Cursor cursor, int displayNameColumn, int lookupKeyColumn) { |
| final String displayName = cursor.getString(displayNameColumn); |
| final String lookupKey = cursor.getString(lookupKeyColumn); |
| return new DefaultImageRequest(displayName, lookupKey, mCircularPhotos); |
| } |
| } |