summaryrefslogtreecommitdiff
path: root/java/com/android/contacts/common/list
diff options
context:
space:
mode:
authorcalderwoodra <calderwoodra@google.com>2018-03-21 16:57:10 -0700
committerCopybara-Service <copybara-piper@google.com>2018-03-26 22:14:35 -0700
commit58d0b2a7cdd4b988f527f03a7cb4ba2a4b7cd145 (patch)
tree9f345f599109739aa93fe700981fc289dab0e66a /java/com/android/contacts/common/list
parent2213f8e4a91bf5bd39e8f55554f7020f0e6b5792 (diff)
Delete old search, old contacts, p13n logger, filtered numbers add number search.
These components are safe to delete because: - New Contacts has been in prod for several releases. - New Search has been in in prod for 2 releases. - p13n logger was based on old search and is no longer being implemented in Dialer. - Filtered Number Settings contact search since we no longer support M. Bug: 37208802,73902692 Test: tap PiperOrigin-RevId: 189992017 Change-Id: I2720a252ababd164b5d0fb1011753a3c96a704d1
Diffstat (limited to 'java/com/android/contacts/common/list')
-rw-r--r--java/com/android/contacts/common/list/AutoScrollListView.java125
-rw-r--r--java/com/android/contacts/common/list/ContactEntryListAdapter.java772
-rw-r--r--java/com/android/contacts/common/list/ContactEntryListFragment.java860
-rw-r--r--java/com/android/contacts/common/list/ContactListAdapter.java232
-rw-r--r--java/com/android/contacts/common/list/ContactListItemView.java1508
-rw-r--r--java/com/android/contacts/common/list/ContactListPinnedHeaderView.java70
-rw-r--r--java/com/android/contacts/common/list/ContactsSectionIndexer.java119
-rw-r--r--java/com/android/contacts/common/list/DefaultContactListAdapter.java216
-rw-r--r--java/com/android/contacts/common/list/DirectoryListLoader.java210
-rw-r--r--java/com/android/contacts/common/list/DirectoryPartition.java179
-rw-r--r--java/com/android/contacts/common/list/IndexerListAdapter.java214
-rw-r--r--java/com/android/contacts/common/list/PhoneNumberListAdapter.java656
-rw-r--r--java/com/android/contacts/common/list/PhoneNumberPickerFragment.java465
-rw-r--r--java/com/android/contacts/common/list/PinnedHeaderListAdapter.java159
-rw-r--r--java/com/android/contacts/common/list/PinnedHeaderListView.java563
15 files changed, 0 insertions, 6348 deletions
diff --git a/java/com/android/contacts/common/list/AutoScrollListView.java b/java/com/android/contacts/common/list/AutoScrollListView.java
deleted file mode 100644
index 8d6455f7f..000000000
--- a/java/com/android/contacts/common/list/AutoScrollListView.java
+++ /dev/null
@@ -1,125 +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.common.list;
-
-import android.content.Context;
-import android.os.Build;
-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);
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
-
- // Workaround for a bug and a bug.
- if (android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.N
- || android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
- layoutChildren();
- }
- }
-}
diff --git a/java/com/android/contacts/common/list/ContactEntryListAdapter.java b/java/com/android/contacts/common/list/ContactEntryListAdapter.java
deleted file mode 100644
index 413a1deff..000000000
--- a/java/com/android/contacts/common/list/ContactEntryListAdapter.java
+++ /dev/null
@@ -1,772 +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.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);
- }
-}
diff --git a/java/com/android/contacts/common/list/ContactEntryListFragment.java b/java/com/android/contacts/common/list/ContactEntryListFragment.java
deleted file mode 100644
index c807ea815..000000000
--- a/java/com/android/contacts/common/list/ContactEntryListFragment.java
+++ /dev/null
@@ -1,860 +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.common.list;
-
-import android.app.Fragment;
-import android.app.LoaderManager;
-import android.app.LoaderManager.LoaderCallbacks;
-import android.content.Context;
-import android.content.CursorLoader;
-import android.content.Loader;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Parcelable;
-import android.provider.ContactsContract.Directory;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnFocusChangeListener;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ListView;
-import com.android.common.widget.CompositeCursorAdapter.Partition;
-import com.android.contacts.common.preference.ContactsPreferences;
-import com.android.contacts.common.util.ContactListViewUtils;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.contactphoto.ContactPhotoManager;
-import com.android.dialer.performancereport.PerformanceReport;
-import java.lang.ref.WeakReference;
-import java.util.Locale;
-
-/** Common base class for various contact-related list fragments. */
-public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter> extends Fragment
- implements OnItemClickListener,
- OnScrollListener,
- OnFocusChangeListener,
- OnTouchListener,
- LoaderCallbacks<Cursor> {
- private static final String KEY_LIST_STATE = "liststate";
- private static final String KEY_SECTION_HEADER_DISPLAY_ENABLED = "sectionHeaderDisplayEnabled";
- private static final String KEY_PHOTO_LOADER_ENABLED = "photoLoaderEnabled";
- private static final String KEY_QUICK_CONTACT_ENABLED = "quickContactEnabled";
- private static final String KEY_ADJUST_SELECTION_BOUNDS_ENABLED = "adjustSelectionBoundsEnabled";
- private static final String KEY_INCLUDE_PROFILE = "includeProfile";
- private static final String KEY_SEARCH_MODE = "searchMode";
- private static final String KEY_VISIBLE_SCROLLBAR_ENABLED = "visibleScrollbarEnabled";
- private static final String KEY_SCROLLBAR_POSITION = "scrollbarPosition";
- private static final String KEY_QUERY_STRING = "queryString";
- private static final String KEY_DIRECTORY_SEARCH_MODE = "directorySearchMode";
- private static final String KEY_SELECTION_VISIBLE = "selectionVisible";
- private static final String KEY_DARK_THEME = "darkTheme";
- private static final String KEY_LEGACY_COMPATIBILITY = "legacyCompatibility";
- private static final String KEY_DIRECTORY_RESULT_LIMIT = "directoryResultLimit";
-
- private static final String DIRECTORY_ID_ARG_KEY = "directoryId";
-
- private static final int DIRECTORY_LOADER_ID = -1;
-
- private static final int DIRECTORY_SEARCH_DELAY_MILLIS = 300;
- private static final int DIRECTORY_SEARCH_MESSAGE = 1;
-
- private static final int DEFAULT_DIRECTORY_RESULT_LIMIT = 20;
- private static final int STATUS_NOT_LOADED = 0;
- private static final int STATUS_LOADING = 1;
- private static final int STATUS_LOADED = 2;
- protected boolean mUserProfileExists;
- private boolean mSectionHeaderDisplayEnabled;
- private boolean mPhotoLoaderEnabled;
- private boolean mQuickContactEnabled = true;
- private boolean mAdjustSelectionBoundsEnabled = true;
- private boolean mIncludeProfile;
- private boolean mSearchMode;
- private boolean mVisibleScrollbarEnabled;
- private boolean mShowEmptyListForEmptyQuery;
- private int mVerticalScrollbarPosition = getDefaultVerticalScrollbarPosition();
- private String mQueryString;
- private int mDirectorySearchMode = DirectoryListLoader.SEARCH_MODE_NONE;
- private boolean mSelectionVisible;
- private boolean mLegacyCompatibility;
- private boolean mEnabled = true;
- private T mAdapter;
- private View mView;
- private ListView mListView;
- /** Used to save the scrolling state of the list when the fragment is not recreated. */
- private int mListViewTopIndex;
-
- private int mListViewTopOffset;
- /** Used for keeping track of the scroll state of the list. */
- private Parcelable mListState;
-
- private int mDisplayOrder;
- private int mSortOrder;
- private int mDirectoryResultLimit = DEFAULT_DIRECTORY_RESULT_LIMIT;
- private ContactPhotoManager mPhotoManager;
- private ContactsPreferences mContactsPrefs;
- private boolean mForceLoad;
- private boolean mDarkTheme;
- private int mDirectoryListStatus = STATUS_NOT_LOADED;
-
- /**
- * Indicates whether we are doing the initial complete load of data (false) or a refresh caused by
- * a change notification (true)
- */
- private boolean mLoadPriorityDirectoriesOnly;
-
- private Context mContext;
-
- private LoaderManager mLoaderManager;
-
- private Handler mDelayedDirectorySearchHandler;
-
- private static class DelayedDirectorySearchHandler extends Handler {
- private final WeakReference<ContactEntryListFragment<?>> contactEntryListFragmentRef;
-
- private DelayedDirectorySearchHandler(ContactEntryListFragment<?> contactEntryListFragment) {
- this.contactEntryListFragmentRef = new WeakReference<>(contactEntryListFragment);
- }
-
- @Override
- public void handleMessage(Message msg) {
- ContactEntryListFragment<?> contactEntryListFragment = contactEntryListFragmentRef.get();
- if (contactEntryListFragment == null) {
- return;
- }
- if (msg.what == DIRECTORY_SEARCH_MESSAGE) {
- contactEntryListFragment.loadDirectoryPartition(msg.arg1, (DirectoryPartition) msg.obj);
- }
- }
- }
-
- private ContactsPreferences.ChangeListener mPreferencesChangeListener =
- new ContactsPreferences.ChangeListener() {
- @Override
- public void onChange() {
- loadPreferences();
- reloadData();
- }
- };
-
- protected ContactEntryListFragment() {
- mDelayedDirectorySearchHandler = new DelayedDirectorySearchHandler(this);
- }
-
- protected abstract View inflateView(LayoutInflater inflater, ViewGroup container);
-
- protected abstract T createListAdapter();
-
- /**
- * @param position Please note that the position is already adjusted for header views, so "0"
- * means the first list item below header views.
- */
- protected abstract void onItemClick(int position, long id);
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- setContext(context);
- setLoaderManager(super.getLoaderManager());
- }
-
- @Override
- public Context getContext() {
- return mContext;
- }
-
- /** Sets a context for the fragment in the unit test environment. */
- public void setContext(Context context) {
- mContext = context;
- configurePhotoLoader();
- }
-
- public void setEnabled(boolean enabled) {
- if (mEnabled != enabled) {
- mEnabled = enabled;
- if (mAdapter != null) {
- if (mEnabled) {
- reloadData();
- } else {
- mAdapter.clearPartitions();
- }
- }
- }
- }
-
- @Override
- public LoaderManager getLoaderManager() {
- return mLoaderManager;
- }
-
- /** Overrides a loader manager for use in unit tests. */
- public void setLoaderManager(LoaderManager loaderManager) {
- mLoaderManager = loaderManager;
- }
-
- public T getAdapter() {
- return mAdapter;
- }
-
- @Override
- public View getView() {
- return mView;
- }
-
- public ListView getListView() {
- return mListView;
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED, mSectionHeaderDisplayEnabled);
- outState.putBoolean(KEY_PHOTO_LOADER_ENABLED, mPhotoLoaderEnabled);
- outState.putBoolean(KEY_QUICK_CONTACT_ENABLED, mQuickContactEnabled);
- outState.putBoolean(KEY_ADJUST_SELECTION_BOUNDS_ENABLED, mAdjustSelectionBoundsEnabled);
- outState.putBoolean(KEY_INCLUDE_PROFILE, mIncludeProfile);
- outState.putBoolean(KEY_SEARCH_MODE, mSearchMode);
- outState.putBoolean(KEY_VISIBLE_SCROLLBAR_ENABLED, mVisibleScrollbarEnabled);
- outState.putInt(KEY_SCROLLBAR_POSITION, mVerticalScrollbarPosition);
- outState.putInt(KEY_DIRECTORY_SEARCH_MODE, mDirectorySearchMode);
- outState.putBoolean(KEY_SELECTION_VISIBLE, mSelectionVisible);
- outState.putBoolean(KEY_LEGACY_COMPATIBILITY, mLegacyCompatibility);
- outState.putString(KEY_QUERY_STRING, mQueryString);
- outState.putInt(KEY_DIRECTORY_RESULT_LIMIT, mDirectoryResultLimit);
- outState.putBoolean(KEY_DARK_THEME, mDarkTheme);
-
- if (mListView != null) {
- outState.putParcelable(KEY_LIST_STATE, mListView.onSaveInstanceState());
- }
- }
-
- @Override
- public void onCreate(Bundle savedState) {
- super.onCreate(savedState);
- restoreSavedState(savedState);
- mAdapter = createListAdapter();
- mContactsPrefs = new ContactsPreferences(mContext);
- }
-
- public void restoreSavedState(Bundle savedState) {
- if (savedState == null) {
- return;
- }
-
- mSectionHeaderDisplayEnabled = savedState.getBoolean(KEY_SECTION_HEADER_DISPLAY_ENABLED);
- mPhotoLoaderEnabled = savedState.getBoolean(KEY_PHOTO_LOADER_ENABLED);
- mQuickContactEnabled = savedState.getBoolean(KEY_QUICK_CONTACT_ENABLED);
- mAdjustSelectionBoundsEnabled = savedState.getBoolean(KEY_ADJUST_SELECTION_BOUNDS_ENABLED);
- mIncludeProfile = savedState.getBoolean(KEY_INCLUDE_PROFILE);
- mSearchMode = savedState.getBoolean(KEY_SEARCH_MODE);
- mVisibleScrollbarEnabled = savedState.getBoolean(KEY_VISIBLE_SCROLLBAR_ENABLED);
- mVerticalScrollbarPosition = savedState.getInt(KEY_SCROLLBAR_POSITION);
- mDirectorySearchMode = savedState.getInt(KEY_DIRECTORY_SEARCH_MODE);
- mSelectionVisible = savedState.getBoolean(KEY_SELECTION_VISIBLE);
- mLegacyCompatibility = savedState.getBoolean(KEY_LEGACY_COMPATIBILITY);
- mQueryString = savedState.getString(KEY_QUERY_STRING);
- mDirectoryResultLimit = savedState.getInt(KEY_DIRECTORY_RESULT_LIMIT);
- mDarkTheme = savedState.getBoolean(KEY_DARK_THEME);
-
- // Retrieve list state. This will be applied in onLoadFinished
- mListState = savedState.getParcelable(KEY_LIST_STATE);
- }
-
- @Override
- public void onStart() {
- super.onStart();
-
- mContactsPrefs.registerChangeListener(mPreferencesChangeListener);
-
- mForceLoad = loadPreferences();
-
- mDirectoryListStatus = STATUS_NOT_LOADED;
- mLoadPriorityDirectoriesOnly = true;
-
- startLoading();
- }
-
- protected void startLoading() {
- if (mAdapter == null) {
- // The method was called before the fragment was started
- return;
- }
-
- configureAdapter();
- int partitionCount = mAdapter.getPartitionCount();
- for (int i = 0; i < partitionCount; i++) {
- Partition partition = mAdapter.getPartition(i);
- if (partition instanceof DirectoryPartition) {
- DirectoryPartition directoryPartition = (DirectoryPartition) partition;
- if (directoryPartition.getStatus() == DirectoryPartition.STATUS_NOT_LOADED) {
- if (directoryPartition.isPriorityDirectory() || !mLoadPriorityDirectoriesOnly) {
- startLoadingDirectoryPartition(i);
- }
- }
- } else {
- getLoaderManager().initLoader(i, null, this);
- }
- }
-
- // Next time this method is called, we should start loading non-priority directories
- mLoadPriorityDirectoriesOnly = false;
- }
-
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- if (id == DIRECTORY_LOADER_ID) {
- DirectoryListLoader loader = new DirectoryListLoader(mContext);
- loader.setDirectorySearchMode(mAdapter.getDirectorySearchMode());
- loader.setLocalInvisibleDirectoryEnabled(
- ContactEntryListAdapter.LOCAL_INVISIBLE_DIRECTORY_ENABLED);
- return loader;
- } else {
- CursorLoader loader = createCursorLoader(mContext);
- long directoryId =
- args != null && args.containsKey(DIRECTORY_ID_ARG_KEY)
- ? args.getLong(DIRECTORY_ID_ARG_KEY)
- : Directory.DEFAULT;
- mAdapter.configureLoader(loader, directoryId);
- return loader;
- }
- }
-
- public CursorLoader createCursorLoader(Context context) {
- return new CursorLoader(context, null, null, null, null, null) {
- @Override
- protected Cursor onLoadInBackground() {
- try {
- return super.onLoadInBackground();
- } catch (RuntimeException e) {
- // We don't even know what the projection should be, so no point trying to
- // return an empty MatrixCursor with the correct projection here.
- LogUtil.w(
- "ContactEntryListFragment.onLoadInBackground",
- "RuntimeException while trying to query ContactsProvider.");
- return null;
- }
- }
- };
- }
-
- private void startLoadingDirectoryPartition(int partitionIndex) {
- DirectoryPartition partition = (DirectoryPartition) mAdapter.getPartition(partitionIndex);
- partition.setStatus(DirectoryPartition.STATUS_LOADING);
- long directoryId = partition.getDirectoryId();
- if (mForceLoad) {
- if (directoryId == Directory.DEFAULT) {
- loadDirectoryPartition(partitionIndex, partition);
- } else {
- loadDirectoryPartitionDelayed(partitionIndex, partition);
- }
- } else {
- Bundle args = new Bundle();
- args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);
- getLoaderManager().initLoader(partitionIndex, args, this);
- }
- }
-
- /**
- * Queues up a delayed request to search the specified directory. Since directory search will
- * likely introduce a lot of network traffic, we want to wait for a pause in the user's typing
- * before sending a directory request.
- */
- private void loadDirectoryPartitionDelayed(int partitionIndex, DirectoryPartition partition) {
- mDelayedDirectorySearchHandler.removeMessages(DIRECTORY_SEARCH_MESSAGE, partition);
- Message msg =
- mDelayedDirectorySearchHandler.obtainMessage(
- DIRECTORY_SEARCH_MESSAGE, partitionIndex, 0, partition);
- mDelayedDirectorySearchHandler.sendMessageDelayed(msg, DIRECTORY_SEARCH_DELAY_MILLIS);
- }
-
- /** Loads the directory partition. */
- protected void loadDirectoryPartition(int partitionIndex, DirectoryPartition partition) {
- Bundle args = new Bundle();
- args.putLong(DIRECTORY_ID_ARG_KEY, partition.getDirectoryId());
- getLoaderManager().restartLoader(partitionIndex, args, this);
- }
-
- /** Cancels all queued directory loading requests. */
- private void removePendingDirectorySearchRequests() {
- mDelayedDirectorySearchHandler.removeMessages(DIRECTORY_SEARCH_MESSAGE);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- if (!mEnabled) {
- return;
- }
-
- int loaderId = loader.getId();
- if (loaderId == DIRECTORY_LOADER_ID) {
- mDirectoryListStatus = STATUS_LOADED;
- mAdapter.changeDirectories(data);
- startLoading();
- } else {
- onPartitionLoaded(loaderId, data);
- if (isSearchMode()) {
- int directorySearchMode = getDirectorySearchMode();
- if (directorySearchMode != DirectoryListLoader.SEARCH_MODE_NONE) {
- if (mDirectoryListStatus == STATUS_NOT_LOADED) {
- mDirectoryListStatus = STATUS_LOADING;
- getLoaderManager().initLoader(DIRECTORY_LOADER_ID, null, this);
- } else {
- startLoading();
- }
- }
- } else {
- mDirectoryListStatus = STATUS_NOT_LOADED;
- getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID);
- }
- }
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {}
-
- protected void onPartitionLoaded(int partitionIndex, Cursor data) {
- if (partitionIndex >= mAdapter.getPartitionCount()) {
- // When we get unsolicited data, ignore it. This could happen
- // when we are switching from search mode to the default mode.
- return;
- }
-
- // Return for non-"Suggestions" if on the zero-suggest screen.
- if (TextUtils.isEmpty(mQueryString) && partitionIndex > 0) {
- return;
- }
-
- mAdapter.changeCursor(partitionIndex, data);
- setProfileHeader();
-
- if (!isLoading()) {
- completeRestoreInstanceState();
- }
- }
-
- public boolean isLoading() {
- //noinspection SimplifiableIfStatement
- if (mAdapter != null && mAdapter.isLoading()) {
- return true;
- }
-
- return isLoadingDirectoryList();
-
- }
-
- public boolean isLoadingDirectoryList() {
- return isSearchMode()
- && getDirectorySearchMode() != DirectoryListLoader.SEARCH_MODE_NONE
- && (mDirectoryListStatus == STATUS_NOT_LOADED || mDirectoryListStatus == STATUS_LOADING);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- mContactsPrefs.unregisterChangeListener();
- mAdapter.clearPartitions();
- }
-
- protected void reloadData() {
- removePendingDirectorySearchRequests();
- mAdapter.onDataReload();
- mLoadPriorityDirectoriesOnly = true;
- mForceLoad = true;
- startLoading();
- }
-
- /**
- * Shows a view at the top of the list with a pseudo local profile prompting the user to add a
- * local profile. Default implementation does nothing.
- */
- protected void setProfileHeader() {
- mUserProfileExists = false;
- }
-
- /** Provides logic that dismisses this fragment. The default implementation does nothing. */
- protected void finish() {}
-
- public boolean isSectionHeaderDisplayEnabled() {
- return mSectionHeaderDisplayEnabled;
- }
-
- public void setSectionHeaderDisplayEnabled(boolean flag) {
- if (mSectionHeaderDisplayEnabled != flag) {
- mSectionHeaderDisplayEnabled = flag;
- if (mAdapter != null) {
- mAdapter.setSectionHeaderDisplayEnabled(flag);
- }
- configureVerticalScrollbar();
- }
- }
-
- public boolean isVisibleScrollbarEnabled() {
- return mVisibleScrollbarEnabled;
- }
-
- public void setVisibleScrollbarEnabled(boolean flag) {
- if (mVisibleScrollbarEnabled != flag) {
- mVisibleScrollbarEnabled = flag;
- configureVerticalScrollbar();
- }
- }
-
- private void configureVerticalScrollbar() {
- boolean hasScrollbar = isVisibleScrollbarEnabled() && isSectionHeaderDisplayEnabled();
-
- if (mListView != null) {
- mListView.setFastScrollEnabled(hasScrollbar);
- mListView.setVerticalScrollbarPosition(mVerticalScrollbarPosition);
- mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
- }
- }
-
- public boolean isPhotoLoaderEnabled() {
- return mPhotoLoaderEnabled;
- }
-
- public void setPhotoLoaderEnabled(boolean flag) {
- mPhotoLoaderEnabled = flag;
- configurePhotoLoader();
- }
-
- public void setQuickContactEnabled(boolean flag) {
- this.mQuickContactEnabled = flag;
- }
-
- public void setAdjustSelectionBoundsEnabled(boolean flag) {
- mAdjustSelectionBoundsEnabled = flag;
- }
-
- public final boolean isSearchMode() {
- return mSearchMode;
- }
-
- /**
- * Enter/exit search mode. This is method is tightly related to the current query, and should only
- * be called by {@link #setQueryString}.
- *
- * <p>Also note this method doesn't call {@link #reloadData()}; {@link #setQueryString} does it.
- */
- protected void setSearchMode(boolean flag) {
- if (mSearchMode != flag) {
- mSearchMode = flag;
-
- if (!flag) {
- mDirectoryListStatus = STATUS_NOT_LOADED;
- getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID);
- }
-
- if (mAdapter != null) {
- mAdapter.setSearchMode(flag);
-
- mAdapter.clearPartitions();
- if (!flag) {
- // If we are switching from search to regular display, remove all directory
- // partitions after default one, assuming they are remote directories which
- // should be cleaned up on exiting the search mode.
- mAdapter.removeDirectoriesAfterDefault();
- }
- mAdapter.configurePartitionsVisibility(flag);
- }
-
- if (mListView != null) {
- mListView.setFastScrollEnabled(!flag);
- }
- }
- }
-
- @Nullable
- public final String getQueryString() {
- return mQueryString;
- }
-
- public void setQueryString(String queryString) {
- if (!TextUtils.equals(mQueryString, queryString)) {
- if (mShowEmptyListForEmptyQuery && mAdapter != null && mListView != null) {
- if (TextUtils.isEmpty(mQueryString)) {
- // Restore the adapter if the query used to be empty.
- mListView.setAdapter(mAdapter);
- } else if (TextUtils.isEmpty(queryString)) {
- // Instantly clear the list view if the new query is empty.
- mListView.setAdapter(null);
- }
- }
-
- mQueryString = queryString;
- setSearchMode(!TextUtils.isEmpty(mQueryString) || mShowEmptyListForEmptyQuery);
-
- if (mAdapter != null) {
- mAdapter.setQueryString(queryString);
- reloadData();
- }
- }
- }
-
- public void setShowEmptyListForNullQuery(boolean show) {
- mShowEmptyListForEmptyQuery = show;
- }
-
- public boolean getShowEmptyListForNullQuery() {
- return mShowEmptyListForEmptyQuery;
- }
-
- public int getDirectoryLoaderId() {
- return DIRECTORY_LOADER_ID;
- }
-
- public int getDirectorySearchMode() {
- return mDirectorySearchMode;
- }
-
- public void setDirectorySearchMode(int mode) {
- mDirectorySearchMode = mode;
- }
-
- protected int getContactNameDisplayOrder() {
- return mDisplayOrder;
- }
-
- protected void setContactNameDisplayOrder(int displayOrder) {
- mDisplayOrder = displayOrder;
- if (mAdapter != null) {
- mAdapter.setContactNameDisplayOrder(displayOrder);
- }
- }
-
- public int getSortOrder() {
- return mSortOrder;
- }
-
- public void setSortOrder(int sortOrder) {
- mSortOrder = sortOrder;
- if (mAdapter != null) {
- mAdapter.setSortOrder(sortOrder);
- }
- }
-
- public void setDirectoryResultLimit(int limit) {
- mDirectoryResultLimit = limit;
- }
-
- protected boolean loadPreferences() {
- boolean changed = false;
- if (getContactNameDisplayOrder() != mContactsPrefs.getDisplayOrder()) {
- setContactNameDisplayOrder(mContactsPrefs.getDisplayOrder());
- changed = true;
- }
-
- if (getSortOrder() != mContactsPrefs.getSortOrder()) {
- setSortOrder(mContactsPrefs.getSortOrder());
- changed = true;
- }
-
- return changed;
- }
-
- @Override
- public View onCreateView(
- LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- onCreateView(inflater, container);
-
- boolean searchMode = isSearchMode();
- mAdapter.setSearchMode(searchMode);
- mAdapter.configurePartitionsVisibility(searchMode);
- mAdapter.setPhotoLoader(mPhotoManager);
- mListView.setAdapter(mAdapter);
- return mView;
- }
-
- protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
- mView = inflateView(inflater, container);
-
- mListView = mView.findViewById(android.R.id.list);
- if (mListView == null) {
- throw new RuntimeException(
- "Your content must have a ListView whose id attribute is " + "'android.R.id.list'");
- }
-
- View emptyView = mView.findViewById(android.R.id.empty);
- if (emptyView != null) {
- mListView.setEmptyView(emptyView);
- }
-
- mListView.setOnItemClickListener(this);
- mListView.setOnFocusChangeListener(this);
- mListView.setOnTouchListener(this);
- mListView.setFastScrollEnabled(!isSearchMode());
-
- // Tell list view to not show dividers. We'll do it ourself so that we can *not* show
- // them when an A-Z headers is visible.
- mListView.setDividerHeight(0);
-
- // We manually save/restore the listview state
- mListView.setSaveEnabled(false);
-
- configureVerticalScrollbar();
- configurePhotoLoader();
-
- getAdapter().setFragmentRootView(getView());
-
- ContactListViewUtils.applyCardPaddingToView(getResources(), mListView, mView);
- }
-
- @Override
- public void onHiddenChanged(boolean hidden) {
- super.onHiddenChanged(hidden);
- if (getActivity() != null && getView() != null && !hidden) {
- // If the padding was last applied when in a hidden state, it may have been applied
- // incorrectly. Therefore we need to reapply it.
- ContactListViewUtils.applyCardPaddingToView(getResources(), mListView, getView());
- }
- }
-
- protected void configurePhotoLoader() {
- if (isPhotoLoaderEnabled() && mContext != null) {
- if (mPhotoManager == null) {
- mPhotoManager = ContactPhotoManager.getInstance(mContext);
- }
- if (mListView != null) {
- mListView.setOnScrollListener(this);
- }
- if (mAdapter != null) {
- mAdapter.setPhotoLoader(mPhotoManager);
- }
- }
- }
-
- protected void configureAdapter() {
- if (mAdapter == null) {
- return;
- }
-
- mAdapter.setQuickContactEnabled(mQuickContactEnabled);
- mAdapter.setAdjustSelectionBoundsEnabled(mAdjustSelectionBoundsEnabled);
- mAdapter.setQueryString(mQueryString);
- mAdapter.setDirectorySearchMode(mDirectorySearchMode);
- mAdapter.setPinnedPartitionHeadersEnabled(false);
- mAdapter.setContactNameDisplayOrder(mDisplayOrder);
- mAdapter.setSortOrder(mSortOrder);
- mAdapter.setSectionHeaderDisplayEnabled(mSectionHeaderDisplayEnabled);
- mAdapter.setSelectionVisible(mSelectionVisible);
- mAdapter.setDirectoryResultLimit(mDirectoryResultLimit);
- mAdapter.setDarkTheme(mDarkTheme);
- }
-
- @Override
- public void onScroll(
- AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}
-
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- PerformanceReport.recordScrollStateChange(scrollState);
- if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
- mPhotoManager.pause();
- } else if (isPhotoLoaderEnabled()) {
- mPhotoManager.resume();
- }
- }
-
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- hideSoftKeyboard();
-
- int adjPosition = position - mListView.getHeaderViewsCount();
- if (adjPosition >= 0) {
- onItemClick(adjPosition, id);
- }
- }
-
- private void hideSoftKeyboard() {
- // Hide soft keyboard, if visible
- InputMethodManager inputMethodManager =
- (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
- inputMethodManager.hideSoftInputFromWindow(mListView.getWindowToken(), 0);
- }
-
- /** Dismisses the soft keyboard when the list takes focus. */
- @Override
- public void onFocusChange(View view, boolean hasFocus) {
- if (view == mListView && hasFocus) {
- hideSoftKeyboard();
- }
- }
-
- /** Dismisses the soft keyboard when the list is touched. */
- @Override
- public boolean onTouch(View view, MotionEvent event) {
- if (view == mListView) {
- hideSoftKeyboard();
- }
- return false;
- }
-
- @Override
- public void onPause() {
- // Save the scrolling state of the list view
- mListViewTopIndex = mListView.getFirstVisiblePosition();
- View v = mListView.getChildAt(0);
- mListViewTopOffset = (v == null) ? 0 : (v.getTop() - mListView.getPaddingTop());
-
- super.onPause();
- removePendingDirectorySearchRequests();
- }
-
- @Override
- public void onResume() {
- super.onResume();
- // Restore the selection of the list view. See a bug.
- // This has to be done manually because if the list view has its emptyView set,
- // the scrolling state will be reset when clearPartitions() is called on the adapter.
- mListView.setSelectionFromTop(mListViewTopIndex, mListViewTopOffset);
- }
-
- /** Restore the list state after the adapter is populated. */
- protected void completeRestoreInstanceState() {
- if (mListState != null) {
- mListView.onRestoreInstanceState(mListState);
- mListState = null;
- }
- }
-
- public void setDarkTheme(boolean value) {
- mDarkTheme = value;
- if (mAdapter != null) {
- mAdapter.setDarkTheme(value);
- }
- }
-
- private int getDefaultVerticalScrollbarPosition() {
- final Locale locale = Locale.getDefault();
- final int layoutDirection = TextUtils.getLayoutDirectionFromLocale(locale);
- switch (layoutDirection) {
- case View.LAYOUT_DIRECTION_RTL:
- return View.SCROLLBAR_POSITION_LEFT;
- case View.LAYOUT_DIRECTION_LTR:
- default:
- return View.SCROLLBAR_POSITION_RIGHT;
- }
- }
-}
diff --git a/java/com/android/contacts/common/list/ContactListAdapter.java b/java/com/android/contacts/common/list/ContactListAdapter.java
deleted file mode 100644
index 721609d1d..000000000
--- a/java/com/android/contacts/common/list/ContactListAdapter.java
+++ /dev/null
@@ -1,232 +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.common.list;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Directory;
-import android.provider.ContactsContract.SearchSnippets;
-import android.view.ViewGroup;
-import com.android.contacts.common.R;
-import com.android.contacts.common.preference.ContactsPreferences;
-import com.android.dialer.contactphoto.ContactPhotoManager.DefaultImageRequest;
-
-/**
- * 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 {
-
- private CharSequence mUnknownNameText;
-
- public ContactListAdapter(Context context) {
- super(context);
-
- mUnknownNameText = context.getText(R.string.missing_name);
- }
-
- protected static Uri buildSectionIndexerUri(Uri uri) {
- return uri.buildUpon().appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true").build();
- }
-
- 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 (uri != null && directoryId != Directory.DEFAULT) {
- uri =
- uri.buildUpon()
- .appendQueryParameter(
- ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId))
- .build();
- }
- return uri;
- }
-
- @Override
- protected ContactListItemView newView(
- Context context, int partition, Cursor cursor, int position, ViewGroup parent) {
- ContactListItemView view = super.newView(context, partition, cursor, position, parent);
- view.setUnknownNameText(mUnknownNameText);
- view.setQuickContactEnabled(isQuickContactEnabled());
- view.setAdjustSelectionBoundsEnabled(isAdjustSelectionBoundsEnabled());
- view.setActivatedStateSupported(isSelectionVisible());
- return view;
- }
-
- protected void bindSectionHeaderAndDivider(
- ContactListItemView view, int position, Cursor cursor) {
- view.setIsSectionHeaderEnabled(isSectionHeaderDisplayEnabled());
- if (isSectionHeaderDisplayEnabled()) {
- Placement placement = getItemPlacementInSection(position);
- view.setSectionHeader(placement.sectionHeader);
- } else {
- view.setSectionHeader(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, getCircularPhotos(), null);
- } else {
- final String photoUriString = cursor.getString(ContactQuery.CONTACT_PHOTO_URI);
- final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
- DefaultImageRequest request = null;
- if (photoUri == null) {
- request =
- getDefaultImageRequestFromCursor(
- cursor, ContactQuery.CONTACT_DISPLAY_NAME, ContactQuery.CONTACT_LOOKUP_KEY);
- }
- getPhotoLoader()
- .loadDirectoryPhoto(view.getPhotoView(), photoUri, false, getCircularPhotos(), request);
- }
- }
-
- protected void bindNameAndViewId(final ContactListItemView view, Cursor cursor) {
- view.showDisplayName(cursor, ContactQuery.CONTACT_DISPLAY_NAME);
- // Note: we don't show phonetic any more (See issue 5265330)
-
- bindViewId(view, cursor, ContactQuery.CONTACT_ID);
- }
-
- 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);
- }
-
- @Override
- public void changeCursor(int partitionIndex, Cursor cursor) {
- super.changeCursor(partitionIndex, cursor);
-
- if (cursor == null || !cursor.moveToFirst()) {
- return;
- }
-
- // hasProfile tells whether the first row is a profile
- final boolean hasProfile = cursor.getInt(ContactQuery.CONTACT_IS_USER_PROFILE) == 1;
-
- // Add ME profile on top of favorites
- cursor.moveToFirst();
- setProfileExists(hasProfile);
- }
-
- /** @return Projection useful for children. */
- protected final String[] getProjection(boolean forSearch) {
- final int sortOrder = getContactNameDisplayOrder();
- if (forSearch) {
- if (sortOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
- return ContactQuery.FILTER_PROJECTION_PRIMARY;
- } else {
- return ContactQuery.FILTER_PROJECTION_ALTERNATIVE;
- }
- } else {
- if (sortOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
- return ContactQuery.CONTACT_PROJECTION_PRIMARY;
- } else {
- return ContactQuery.CONTACT_PROJECTION_ALTERNATIVE;
- }
- }
- }
-
- protected static class ContactQuery {
-
- 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_PHONETIC_NAME = 8;
- public static final int CONTACT_STARRED = 9;
- public static final int CONTACT_SNIPPET = 10;
- 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
- Contacts.PHONETIC_NAME, // 8
- Contacts.STARRED, // 9
- };
- 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
- Contacts.PHONETIC_NAME, // 8
- Contacts.STARRED, // 9
- };
- 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
- Contacts.PHONETIC_NAME, // 8
- Contacts.STARRED, // 9
- SearchSnippets.SNIPPET, // 10
- };
- 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
- Contacts.PHONETIC_NAME, // 8
- Contacts.STARRED, // 9
- SearchSnippets.SNIPPET, // 10
- };
- }
-}
diff --git a/java/com/android/contacts/common/list/ContactListItemView.java b/java/com/android/contacts/common/list/ContactListItemView.java
deleted file mode 100644
index 409aa1b9e..000000000
--- a/java/com/android/contacts/common/list/ContactListItemView.java
+++ /dev/null
@@ -1,1508 +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.common.list;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-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.provider.ContactsContract.SearchSnippets;
-import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
-import android.support.v4.content.ContextCompat;
-import android.telephony.PhoneNumberUtils;
-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.MotionEvent;
-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.common.R;
-import com.android.contacts.common.format.TextHighlighter;
-import com.android.contacts.common.list.PhoneNumberListAdapter.Listener;
-import com.android.contacts.common.util.ContactDisplayUtils;
-import com.android.contacts.common.util.SearchUtil;
-import com.android.dialer.callintent.CallIntentBuilder;
-import com.android.dialer.util.ViewUtil;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * 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.
- *
- * <p>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.
- *
- * <p>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 {
-
- /** IntDef for indices of ViewPager tabs. */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({NONE, VIDEO, DUO, CALL_AND_SHARE})
- public @interface CallToAction {}
-
- public static final int NONE = 0;
- public static final int VIDEO = 1;
- public static final int DUO = 2;
- public static final int CALL_AND_SHARE = 3;
-
- private final PhotoPosition mPhotoPosition = getDefaultPhotoPosition();
- private static final Pattern SPLIT_PATTERN =
- Pattern.compile("([\\w-\\.]+)@((?:[\\w]+\\.)+)([a-zA-Z]{2,4})|[\\w]+");
- static final char SNIPPET_START_MATCH = '[';
- static final char SNIPPET_END_MATCH = ']';
- /** A helper used to highlight a prefix in a text field. */
- private final TextHighlighter mTextHighlighter;
- // Style values for layout and appearance
- // The initialized values are defaults if none is provided through xml.
- private int mPreferredHeight = 0;
- private int mGapBetweenImageAndText = 0;
- private int mGapBetweenLabelAndData = 0;
- private int mPresenceIconMargin = 4;
- private int mPresenceIconSize = 16;
- private int mTextIndent = 0;
- private int mTextOffsetTop;
- private int mNameTextViewTextSize;
- private int mHeaderWidth;
- private Drawable mActivatedBackgroundDrawable;
- private int mCallToActionSize = 48;
- private int mCallToActionMargin = 16;
- // Set in onLayout. Represent left and right position of the View on the screen.
- private int mLeftOffset;
- private int mRightOffset;
- /** Used with {@link #mLabelView}, specifying the width ratio between label and data. */
- private int mLabelViewWidthWeight = 3;
- /** Used with {@link #mDataView}, specifying the width ratio between label and data. */
- private int mDataViewWidthWeight = 5;
-
- private ArrayList<HighlightSequence> mNameHighlightSequence;
- private ArrayList<HighlightSequence> mNumberHighlightSequence;
- // Highlighting prefix for names.
- private String mHighlightedPrefix;
- /** Indicates whether the view should leave room for the "video call" icon. */
- private boolean mSupportVideoCall;
-
- // Header layout data
- private TextView mHeaderTextView;
- private boolean mIsSectionHeaderEnabled;
- // The views inside the contact view
- private boolean mQuickContactEnabled = true;
- private QuickContactBadge mQuickContact;
- private ImageView mPhotoView;
- private TextView mNameTextView;
- private TextView mLabelView;
- private TextView mDataView;
- private TextView mSnippetView;
- private TextView mStatusView;
- private ImageView mPresenceIcon;
- @NonNull private final ImageView mCallToActionView;
- private ImageView mWorkProfileIcon;
- private ColorStateList mSecondaryTextColor;
- private int mDefaultPhotoViewSize = 0;
- /**
- * 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 mNameTextViewTextColor = Color.BLACK;
- private int mPhoneticNameTextViewHeight;
- private int mLabelViewHeight;
- private int mDataViewHeight;
- private int mSnippetTextViewHeight;
- private int mStatusTextViewHeight;
- private int mCheckBoxWidth;
- // Holds Math.max(mLabelTextViewHeight, mDataViewHeight), assuming Label and Data share the
- // same row.
- private int mLabelAndDataViewMaxHeight;
- private boolean mActivatedStateSupported;
- private boolean mAdjustSelectionBoundsEnabled = true;
- private Rect mBoundsWithoutHeader = new Rect();
- private CharSequence mUnknownNameText;
-
- private String mPhoneNumber;
- private int mPosition = -1;
- private @CallToAction int mCallToAction = NONE;
-
- public ContactListItemView(Context context, AttributeSet attrs, boolean supportVideoCallIcon) {
- this(context, attrs);
-
- mSupportVideoCall = supportVideoCallIcon;
- }
-
- public ContactListItemView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- TypedArray a;
-
- if (R.styleable.ContactListItemView != null) {
- // Read all style values
- a = getContext().obtainStyledAttributes(attrs, R.styleable.ContactListItemView);
- mPreferredHeight =
- a.getDimensionPixelSize(
- R.styleable.ContactListItemView_list_item_height, mPreferredHeight);
- mActivatedBackgroundDrawable =
- a.getDrawable(R.styleable.ContactListItemView_activated_background);
- mGapBetweenImageAndText =
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_gap_between_image_and_text,
- mGapBetweenImageAndText);
- mGapBetweenLabelAndData =
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_gap_between_label_and_data,
- mGapBetweenLabelAndData);
- mPresenceIconMargin =
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_presence_icon_margin, mPresenceIconMargin);
- mPresenceIconSize =
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_presence_icon_size, mPresenceIconSize);
- mDefaultPhotoViewSize =
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_photo_size, mDefaultPhotoViewSize);
- mTextIndent =
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_text_indent, mTextIndent);
- mTextOffsetTop =
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_text_offset_top, mTextOffsetTop);
- mDataViewWidthWeight =
- a.getInteger(
- R.styleable.ContactListItemView_list_item_data_width_weight, mDataViewWidthWeight);
- mLabelViewWidthWeight =
- a.getInteger(
- R.styleable.ContactListItemView_list_item_label_width_weight, mLabelViewWidthWeight);
- mNameTextViewTextColor =
- a.getColor(
- R.styleable.ContactListItemView_list_item_name_text_color, mNameTextViewTextColor);
- mNameTextViewTextSize =
- (int)
- a.getDimension(
- R.styleable.ContactListItemView_list_item_name_text_size,
- (int) getResources().getDimension(R.dimen.contact_browser_list_item_text_size));
- mCallToActionSize =
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_video_call_icon_size, mCallToActionSize);
- mCallToActionMargin =
- a.getDimensionPixelOffset(
- R.styleable.ContactListItemView_list_item_video_call_icon_margin,
- mCallToActionMargin);
-
- setPaddingRelative(
- 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));
-
- a.recycle();
- }
-
- mTextHighlighter = new TextHighlighter(Typeface.BOLD);
-
- if (R.styleable.Theme != null) {
- a = getContext().obtainStyledAttributes(R.styleable.Theme);
- mSecondaryTextColor = a.getColorStateList(R.styleable.Theme_android_textColorSecondary);
- a.recycle();
- }
-
- mHeaderWidth = getResources().getDimensionPixelSize(R.dimen.contact_list_section_header_width);
-
- if (mActivatedBackgroundDrawable != null) {
- mActivatedBackgroundDrawable.setCallback(this);
- }
-
- mNameHighlightSequence = new ArrayList<>();
- mNumberHighlightSequence = new ArrayList<>();
-
- mCallToActionView = new ImageView(getContext());
- mCallToActionView.setId(R.id.call_to_action);
- mCallToActionView.setLayoutParams(new LayoutParams(mCallToActionSize, mCallToActionSize));
- mCallToActionView.setScaleType(ScaleType.CENTER);
- mCallToActionView.setImageTintList(
- ContextCompat.getColorStateList(getContext(), R.color.search_video_call_icon_tint));
- addView(mCallToActionView);
-
- setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
- }
-
- public static PhotoPosition getDefaultPhotoPosition() {
- int layoutDirection = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
- return layoutDirection == View.LAYOUT_DIRECTION_RTL ? PhotoPosition.RIGHT : PhotoPosition.LEFT;
- }
-
- /**
- * Helper method for splitting a string into tokens. The lists passed in are populated with the
- * tokens and offsets into the content of each token. The tokenization function parses e-mail
- * addresses as a single token; otherwise it splits on any non-alphanumeric character.
- *
- * @param content Content to split.
- * @return List of token strings.
- */
- private static List<String> split(String content) {
- final Matcher matcher = SPLIT_PATTERN.matcher(content);
- final ArrayList<String> tokens = new ArrayList<>();
- while (matcher.find()) {
- tokens.add(matcher.group());
- }
- return tokens;
- }
-
- public void setUnknownNameText(CharSequence unknownNameText) {
- mUnknownNameText = unknownNameText;
- }
-
- public void setQuickContactEnabled(boolean flag) {
- mQuickContactEnabled = flag;
- }
-
- /**
- * Sets whether the call to action is shown. For the {@link CallToAction} to be shown, it must be
- * supported as well.
- *
- * @param action {@link CallToAction} you want to display (if it's supported).
- * @param listener Listener to notify when the call to action is clicked.
- * @param position The position in the adapter of the call to action.
- */
- public void setCallToAction(@CallToAction int action, Listener listener, int position) {
- mCallToAction = action;
- mPosition = position;
-
- Drawable drawable;
- int description;
- OnClickListener onClickListener;
- if (action == CALL_AND_SHARE) {
- drawable = ContextCompat.getDrawable(getContext(), R.drawable.ic_phone_attach);
- drawable.setAutoMirrored(true);
- description = R.string.description_search_call_and_share;
- onClickListener = v -> listener.onCallAndShareIconClicked(position);
- } else if (action == VIDEO && mSupportVideoCall) {
- drawable =
- ContextCompat.getDrawable(getContext(), R.drawable.quantum_ic_videocam_vd_theme_24);
- drawable.setAutoMirrored(true);
- description = R.string.description_search_video_call;
- onClickListener = v -> listener.onVideoCallIconClicked(position);
- } else if (action == DUO) {
- CallIntentBuilder.increaseLightbringerCallButtonAppearInSearchCount();
- drawable =
- ContextCompat.getDrawable(getContext(), R.drawable.quantum_ic_videocam_vd_theme_24);
- drawable.setAutoMirrored(true);
- description = R.string.description_search_video_call;
- onClickListener = v -> listener.onDuoVideoIconClicked(position);
- } else {
- mCallToActionView.setVisibility(View.GONE);
- mCallToActionView.setOnClickListener(null);
- return;
- }
-
- mCallToActionView.setContentDescription(getContext().getString(description));
- mCallToActionView.setOnClickListener(onClickListener);
- mCallToActionView.setImageDrawable(drawable);
- mCallToActionView.setVisibility(View.VISIBLE);
- }
-
- public @CallToAction int getCallToAction() {
- return mCallToAction;
- }
-
- public int getPosition() {
- return mPosition;
- }
-
- /**
- * Sets whether the view supports a video calling icon. This is independent of whether the view is
- * actually showing an icon. Support for the video calling icon ensures that the layout leaves
- * space for the video icon, should it be shown.
- *
- * @param supportVideoCall {@code true} if the video call icon is supported, {@code false}
- * otherwise.
- */
- public void setSupportVideoCallIcon(boolean supportVideoCall) {
- mSupportVideoCall = supportVideoCall;
- }
-
- @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 = mPreferredHeight;
-
- mNameTextViewHeight = 0;
- mPhoneticNameTextViewHeight = 0;
- mLabelViewHeight = 0;
- mDataViewHeight = 0;
- mLabelAndDataViewMaxHeight = 0;
- mSnippetTextViewHeight = 0;
- mStatusTextViewHeight = 0;
- mCheckBoxWidth = 0;
-
- ensurePhotoViewSize();
-
- // Width each TextView is able to use.
- 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();
- }
-
- if (mIsSectionHeaderEnabled) {
- effectiveWidth -= mHeaderWidth + mGapBetweenImageAndText;
- }
-
- effectiveWidth -= (mCallToActionSize + mCallToActionMargin);
-
- // 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)) {
- // Calculate 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 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)) {
- mLabelView.measure(
- MeasureSpec.makeMeasureSpec(labelWidth, MeasureSpec.AT_MOST),
- 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();
- }
-
- mCallToActionView.measure(
- MeasureSpec.makeMeasureSpec(mCallToActionSize, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(mCallToActionSize, MeasureSpec.EXACTLY));
-
- if (isVisible(mWorkProfileIcon)) {
- mWorkProfileIcon.measure(
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- mNameTextViewHeight = Math.max(mNameTextViewHeight, mWorkProfileIcon.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());
-
- // Make sure height is at least the preferred height
- height = Math.max(height, preferredHeight);
-
- // Measure the header if it is visible.
- if (mHeaderTextView != null && mHeaderTextView.getVisibility() == VISIBLE) {
- mHeaderTextView.measure(
- MeasureSpec.makeMeasureSpec(mHeaderWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- }
-
- 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 leftBound = getPaddingLeft();
- int rightBound = width - getPaddingRight();
-
- final boolean isLayoutRtl = ViewUtil.isViewLayoutRtl(this);
-
- // Put the section header on the left side of the contact view.
- if (mIsSectionHeaderEnabled) {
- // Align the text view all the way left, to be consistent with Contacts.
- if (isLayoutRtl) {
- rightBound = width;
- } else {
- leftBound = 0;
- }
- if (mHeaderTextView != null) {
- int headerHeight = mHeaderTextView.getMeasuredHeight();
- int headerTopBound = (height + topBound - headerHeight) / 2 + mTextOffsetTop;
-
- mHeaderTextView.layout(
- isLayoutRtl ? rightBound - mHeaderWidth : leftBound,
- headerTopBound,
- isLayoutRtl ? rightBound : leftBound + mHeaderWidth,
- headerTopBound + headerHeight);
- }
- if (isLayoutRtl) {
- rightBound -= mHeaderWidth;
- } else {
- leftBound += mHeaderWidth;
- }
- }
-
- mBoundsWithoutHeader.set(left + leftBound, topBound, left + rightBound, height);
- mLeftOffset = left + leftBound;
- mRightOffset = left + rightBound;
- if (mIsSectionHeaderEnabled) {
- if (isLayoutRtl) {
- rightBound -= mGapBetweenImageAndText;
- } else {
- leftBound += mGapBetweenImageAndText;
- }
- }
-
- 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 + (height - 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 + (height - topBound - mPhotoViewHeight) / 2;
- photoView.layout(
- rightBound - mPhotoViewWidth, photoTop, rightBound, photoTop + mPhotoViewHeight);
- rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
- } else if (mKeepHorizontalPaddingForPhotoView) {
- // Draw nothing but keep the padding.
- rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
- }
-
- // Add indent between left-most padding and texts.
- leftBound += mTextIndent;
- }
-
- // Place the call to action at the end of the list (e.g. take into account RTL mode).
- // Center the icon vertically
- final int callToActionTop = topBound + (height - topBound - mCallToActionSize) / 2;
-
- if (!isLayoutRtl) {
- // When photo is on left, icon is placed on the right edge.
- mCallToActionView.layout(
- rightBound - mCallToActionSize,
- callToActionTop,
- rightBound,
- callToActionTop + mCallToActionSize);
- } else {
- // When photo is on right, icon is placed on the left edge.
- mCallToActionView.layout(
- leftBound,
- callToActionTop,
- leftBound + mCallToActionSize,
- callToActionTop + mCallToActionSize);
- }
-
- if (mPhotoPosition == PhotoPosition.LEFT) {
- rightBound -= (mCallToActionSize + mCallToActionMargin);
- } else {
- leftBound += mCallToActionSize + mCallToActionMargin;
- }
-
- // Center text vertically, then apply the top offset.
- final int totalTextHeight =
- mNameTextViewHeight
- + mPhoneticNameTextViewHeight
- + mLabelAndDataViewMaxHeight
- + mSnippetTextViewHeight
- + mStatusTextViewHeight;
- int textTopBound = (height + topBound - totalTextHeight) / 2 + mTextOffsetTop;
-
- // Work Profile icon align top
- int workProfileIconWidth = 0;
- if (isVisible(mWorkProfileIcon)) {
- workProfileIconWidth = mWorkProfileIcon.getMeasuredWidth();
- final int distanceFromEnd = mCheckBoxWidth > 0 ? mCheckBoxWidth + mGapBetweenImageAndText : 0;
- if (mPhotoPosition == PhotoPosition.LEFT) {
- // When photo is on left, label is placed on the right edge of the list item.
- mWorkProfileIcon.layout(
- rightBound - workProfileIconWidth - distanceFromEnd,
- textTopBound,
- rightBound - distanceFromEnd,
- textTopBound + mNameTextViewHeight);
- } else {
- // When photo is on right, label is placed on the left of data view.
- mWorkProfileIcon.layout(
- leftBound + distanceFromEnd,
- textTopBound,
- leftBound + workProfileIconWidth + distanceFromEnd,
- textTopBound + mNameTextViewHeight);
- }
- }
-
- // Layout all text view and presence icon
- // Put name TextView first
- if (isVisible(mNameTextView)) {
- final int distanceFromEnd =
- workProfileIconWidth
- + (mCheckBoxWidth > 0 ? mCheckBoxWidth + mGapBetweenImageAndText : 0);
- if (mPhotoPosition == PhotoPosition.LEFT) {
- mNameTextView.layout(
- leftBound,
- textTopBound,
- rightBound - distanceFromEnd,
- textTopBound + mNameTextViewHeight);
- } else {
- mNameTextView.layout(
- leftBound + distanceFromEnd,
- textTopBound,
- rightBound,
- textTopBound + mNameTextViewHeight);
- }
- }
-
- if (isVisible(mNameTextView) || isVisible(mWorkProfileIcon)) {
- textTopBound += mNameTextViewHeight;
- }
-
- // Presence and status
- if (isLayoutRtl) {
- int statusRightBound = rightBound;
- if (isVisible(mPresenceIcon)) {
- int iconWidth = mPresenceIcon.getMeasuredWidth();
- mPresenceIcon.layout(
- rightBound - iconWidth, textTopBound, rightBound, textTopBound + mStatusTextViewHeight);
- statusRightBound -= (iconWidth + mPresenceIconMargin);
- }
-
- if (isVisible(mStatusView)) {
- mStatusView.layout(
- leftBound, textTopBound, statusRightBound, textTopBound + mStatusTextViewHeight);
- }
- } else {
- 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;
-
- // Label and Data align bottom.
- if (isVisible(mLabelView)) {
- if (!isLayoutRtl) {
- mLabelView.layout(
- dataLeftBound,
- textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
- rightBound,
- textTopBound + mLabelAndDataViewMaxHeight);
- dataLeftBound += mLabelView.getMeasuredWidth() + mGapBetweenLabelAndData;
- } else {
- dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
- mLabelView.layout(
- rightBound - mLabelView.getMeasuredWidth(),
- textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
- rightBound,
- textTopBound + mLabelAndDataViewMaxHeight);
- rightBound -= (mLabelView.getMeasuredWidth() + mGapBetweenLabelAndData);
- }
- }
-
- if (isVisible(mDataView)) {
- if (!isLayoutRtl) {
- mDataView.layout(
- dataLeftBound,
- textTopBound + mLabelAndDataViewMaxHeight - mDataViewHeight,
- rightBound,
- textTopBound + mLabelAndDataViewMaxHeight);
- } else {
- mDataView.layout(
- rightBound - mDataView.getMeasuredWidth(),
- 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) {
- if (mAdjustSelectionBoundsEnabled) {
- bounds.top += mBoundsWithoutHeader.top;
- bounds.bottom = bounds.top + mBoundsWithoutHeader.height();
- bounds.left = mBoundsWithoutHeader.left;
- bounds.right = mBoundsWithoutHeader.right;
- }
- }
-
- 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 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);
- }
-
- super.dispatchDraw(canvas);
- }
-
- /** 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(getContext());
- mHeaderTextView.setTextAppearance(R.style.SectionHeaderStyle);
- mHeaderTextView.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL);
- addView(mHeaderTextView);
- }
- setMarqueeText(mHeaderTextView, title);
- mHeaderTextView.setVisibility(View.VISIBLE);
- mHeaderTextView.setAllCaps(true);
- } else if (mHeaderTextView != null) {
- mHeaderTextView.setVisibility(View.GONE);
- }
- }
-
- public void setIsSectionHeaderEnabled(boolean isSectionHeaderEnabled) {
- mIsSectionHeaderEnabled = isSectionHeaderEnabled;
- }
-
- /** 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(getContext());
- mQuickContact.setOverlay(null);
- mQuickContact.setLayoutParams(getDefaultPhotoLayoutParams());
- if (mNameTextView != null) {
- mQuickContact.setContentDescription(
- getContext()
- .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(getContext());
- 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. This will disable the mask highlighting for names.
- *
- * <p>NOTE: must be all upper-case
- */
- public void setHighlightedPrefix(String upperCasePrefix) {
- mHighlightedPrefix = upperCasePrefix;
- }
-
- /** Clears previously set highlight sequences for the view. */
- public void clearHighlightSequences() {
- mNameHighlightSequence.clear();
- mNumberHighlightSequence.clear();
- mHighlightedPrefix = null;
- }
-
- /**
- * Adds a highlight sequence to the name highlighter.
- *
- * @param start The start position of the highlight sequence.
- * @param end The end position of the highlight sequence.
- */
- public void addNameHighlightSequence(int start, int end) {
- mNameHighlightSequence.add(new HighlightSequence(start, end));
- }
-
- /**
- * Adds a highlight sequence to the number highlighter.
- *
- * @param start The start position of the highlight sequence.
- * @param end The end position of the highlight sequence.
- */
- public void addNumberHighlightSequence(int start, int end) {
- mNumberHighlightSequence.add(new HighlightSequence(start, end));
- }
-
- /** Returns the text view for the contact name, creating it if necessary. */
- public TextView getNameTextView() {
- if (mNameTextView == null) {
- mNameTextView = new TextView(getContext());
- mNameTextView.setSingleLine(true);
- mNameTextView.setEllipsize(getTextEllipsis());
- mNameTextView.setTextColor(mNameTextViewTextColor);
- mNameTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mNameTextViewTextSize);
- // 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);
- mNameTextView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
- mNameTextView.setId(R.id.cliv_name_textview);
- mNameTextView.setElegantTextHeight(false);
- addView(mNameTextView);
- }
- return mNameTextView;
- }
-
- /** 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(getContext());
- mLabelView.setLayoutParams(
- new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
-
- mLabelView.setSingleLine(true);
- mLabelView.setEllipsize(getTextEllipsis());
- mLabelView.setTextAppearance(R.style.TextAppearanceSmall);
- if (mPhotoPosition == PhotoPosition.LEFT) {
- mLabelView.setAllCaps(true);
- } else {
- mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
- }
- mLabelView.setActivated(isActivated());
- mLabelView.setId(R.id.cliv_label_textview);
- addView(mLabelView);
- }
- return mLabelView;
- }
-
- /**
- * Sets phone number for a list item. This takes care of number highlighting if the highlight mask
- * exists.
- */
- public void setPhoneNumber(String text) {
- mPhoneNumber = text;
- if (text == null) {
- if (mDataView != null) {
- mDataView.setVisibility(View.GONE);
- }
- } else {
- getDataView();
-
- // TODO: Format number using PhoneNumberUtils.formatNumber before assigning it to
- // mDataView. Make sure that determination of the highlight sequences are done only
- // after number formatting.
-
- // Sets phone number texts for display after highlighting it, if applicable.
- // CharSequence textToSet = text;
- final SpannableString textToSet = new SpannableString(text);
-
- if (mNumberHighlightSequence.size() != 0) {
- final HighlightSequence highlightSequence = mNumberHighlightSequence.get(0);
- mTextHighlighter.applyMaskingHighlight(
- textToSet, highlightSequence.start, highlightSequence.end);
- }
-
- setMarqueeText(mDataView, textToSet);
- mDataView.setVisibility(VISIBLE);
-
- // We have a phone number as "mDataView" so make it always LTR and VIEW_START
- mDataView.setTextDirection(View.TEXT_DIRECTION_LTR);
- mDataView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
- }
- }
-
- public String getPhoneNumber() {
- return mPhoneNumber;
- }
-
- 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(getContext());
- mDataView.setSingleLine(true);
- mDataView.setEllipsize(getTextEllipsis());
- mDataView.setTextAppearance(R.style.TextAppearanceSmall);
- mDataView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
- mDataView.setActivated(isActivated());
- mDataView.setId(R.id.cliv_data_view);
- mDataView.setElegantTextHeight(false);
- 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 {
- mTextHighlighter.setPrefixText(getSnippetView(), text, mHighlightedPrefix);
- mSnippetView.setVisibility(VISIBLE);
- if (ContactDisplayUtils.isPossiblePhoneNumber(text)) {
- // Give the text-to-speech engine a hint that it's a phone number
- mSnippetView.setContentDescription(PhoneNumberUtils.createTtsSpannable(text));
- } else {
- mSnippetView.setContentDescription(null);
- }
- }
- }
-
- /** Returns the text view for the search snippet, creating it if necessary. */
- public TextView getSnippetView() {
- if (mSnippetView == null) {
- mSnippetView = new TextView(getContext());
- mSnippetView.setSingleLine(true);
- mSnippetView.setEllipsize(getTextEllipsis());
- mSnippetView.setTextAppearance(android.R.style.TextAppearance_Small);
- mSnippetView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
- 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(getContext());
- mStatusView.setSingleLine(true);
- mStatusView.setEllipsize(getTextEllipsis());
- mStatusView.setTextAppearance(android.R.style.TextAppearance_Small);
- mStatusView.setTextColor(mSecondaryTextColor);
- mStatusView.setActivated(isActivated());
- mStatusView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
- addView(mStatusView);
- }
- return mStatusView;
- }
-
- /** 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(getContext());
- addView(mPresenceIcon);
- }
- mPresenceIcon.setImageDrawable(icon);
- mPresenceIcon.setScaleType(ScaleType.CENTER);
- mPresenceIcon.setVisibility(View.VISIBLE);
- } else {
- if (mPresenceIcon != null) {
- mPresenceIcon.setVisibility(View.GONE);
- }
- }
- }
-
- /**
- * Set to display work profile icon or not
- *
- * @param enabled set to display work profile icon or not
- */
- public void setWorkProfileIconEnabled(boolean enabled) {
- if (mWorkProfileIcon != null) {
- mWorkProfileIcon.setVisibility(enabled ? View.VISIBLE : View.GONE);
- } else if (enabled) {
- mWorkProfileIcon = new ImageView(getContext());
- addView(mWorkProfileIcon);
- mWorkProfileIcon.setImageResource(R.drawable.ic_work_profile);
- mWorkProfileIcon.setScaleType(ScaleType.CENTER_INSIDE);
- mWorkProfileIcon.setVisibility(View.VISIBLE);
- }
- }
-
- private TruncateAt getTextEllipsis() {
- return TruncateAt.MARQUEE;
- }
-
- public void showDisplayName(Cursor cursor, int nameColumnIndex) {
- CharSequence name = cursor.getString(nameColumnIndex);
- setDisplayName(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(
- getContext().getString(R.string.description_quick_contact_for, mNameTextView.getText()));
- }
- }
-
- public void setDisplayName(CharSequence name) {
- if (!TextUtils.isEmpty(name)) {
- // Chooses the available highlighting method for highlighting.
- if (mHighlightedPrefix != null) {
- name = mTextHighlighter.applyPrefixHighlight(name, mHighlightedPrefix);
- } else if (mNameHighlightSequence.size() != 0) {
- final SpannableString spannableName = new SpannableString(name);
- for (HighlightSequence highlightSequence : mNameHighlightSequence) {
- mTextHighlighter.applyMaskingHighlight(
- spannableName, highlightSequence.start, highlightSequence.end);
- }
- name = spannableName;
- }
- } else {
- name = mUnknownNameText;
- }
- setMarqueeText(getNameTextView(), name);
-
- if (ContactDisplayUtils.isPossiblePhoneNumber(name)) {
- // Give the text-to-speech engine a hint that it's a phone number
- mNameTextView.setTextDirection(View.TEXT_DIRECTION_LTR);
- mNameTextView.setContentDescription(PhoneNumberUtils.createTtsSpannable(name.toString()));
- } else {
- // Remove span tags of highlighting for talkback to avoid reading highlighting and rest
- // of the name into two separate parts.
- mNameTextView.setContentDescription(name.toString());
- }
- }
-
- public void hideDisplayName() {
- if (mNameTextView != null) {
- removeView(mNameTextView);
- mNameTextView = 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
- || !SearchSnippets.SNIPPET.equals(cursor.getColumnName(summarySnippetColumnIndex))) {
- setSnippet(null);
- return;
- }
-
- String snippet = cursor.getString(summarySnippetColumnIndex);
-
- // Do client side snippeting if provider didn't do it
- final Bundle extras = cursor.getExtras();
- if (extras.getBoolean(ContactsContract.DEFERRED_SNIPPETING)) {
-
- final String query = extras.getString(ContactsContract.DEFERRED_SNIPPETING_QUERY);
-
- String displayName = null;
- int displayNameIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
- if (displayNameIndex >= 0) {
- displayName = cursor.getString(displayNameIndex);
- }
-
- snippet = updateSnippet(snippet, query, displayName);
-
- } else {
- if (snippet != null) {
- int from = 0;
- int to = snippet.length();
- int start = snippet.indexOf(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(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 != SNIPPET_START_MATCH && c != SNIPPET_END_MATCH) {
- sb.append(c);
- }
- }
- snippet = sb.toString();
- }
- }
- }
-
- setSnippet(snippet);
- }
-
- /**
- * Used for deferred snippets from the database. The contents come back as large strings which
- * need to be extracted for display.
- *
- * @param snippet The snippet from the database.
- * @param query The search query substring.
- * @param displayName The contact display name.
- * @return The proper snippet to display.
- */
- private String updateSnippet(String snippet, String query, String displayName) {
-
- if (TextUtils.isEmpty(snippet) || TextUtils.isEmpty(query)) {
- return null;
- }
- query = SearchUtil.cleanStartAndEndOfSearchQuery(query.toLowerCase());
-
- // If the display name already contains the query term, return empty - snippets should
- // not be needed in that case.
- if (!TextUtils.isEmpty(displayName)) {
- final String lowerDisplayName = displayName.toLowerCase();
- final List<String> nameTokens = split(lowerDisplayName);
- for (String nameToken : nameTokens) {
- if (nameToken.startsWith(query)) {
- return null;
- }
- }
- }
-
- // The snippet may contain multiple data lines.
- // Show the first line that matches the query.
- final SearchUtil.MatchedLine matched = SearchUtil.findMatchingLine(snippet, query);
-
- if (matched != null && matched.line != null) {
- // Tokenize for long strings since the match may be at the end of it.
- // Skip this part for short strings since the whole string will be displayed.
- // Most contact strings are short so the snippetize method will be called infrequently.
- final int lengthThreshold =
- getResources().getInteger(R.integer.snippet_length_before_tokenize);
- if (matched.line.length() > lengthThreshold) {
- return snippetize(matched.line, matched.startIndex, lengthThreshold);
- } else {
- return matched.line;
- }
- }
-
- // No match found.
- return null;
- }
-
- private String snippetize(String line, int matchIndex, int maxLength) {
- // Show up to maxLength characters. But we only show full tokens so show the last full token
- // up to maxLength characters. So as many starting tokens as possible before trying ending
- // tokens.
- int remainingLength = maxLength;
- int tempRemainingLength = remainingLength;
-
- // Start the end token after the matched query.
- int index = matchIndex;
- int endTokenIndex = index;
-
- // Find the match token first.
- while (index < line.length()) {
- if (!Character.isLetterOrDigit(line.charAt(index))) {
- endTokenIndex = index;
- remainingLength = tempRemainingLength;
- break;
- }
- tempRemainingLength--;
- index++;
- }
-
- // Find as much content before the match.
- index = matchIndex - 1;
- tempRemainingLength = remainingLength;
- int startTokenIndex = matchIndex;
- while (index > -1 && tempRemainingLength > 0) {
- if (!Character.isLetterOrDigit(line.charAt(index))) {
- startTokenIndex = index;
- remainingLength = tempRemainingLength;
- }
- tempRemainingLength--;
- index--;
- }
-
- index = endTokenIndex;
- tempRemainingLength = remainingLength;
- // Find remaining content at after match.
- while (index < line.length() && tempRemainingLength > 0) {
- if (!Character.isLetterOrDigit(line.charAt(index))) {
- endTokenIndex = index;
- }
- tempRemainingLength--;
- index++;
- }
- // Append ellipse if there is content before or after.
- final StringBuilder sb = new StringBuilder();
- if (startTokenIndex > 0) {
- sb.append("...");
- }
- sb.append(line.substring(startTokenIndex, endTokenIndex));
- if (endTokenIndex < line.length()) {
- sb.append("...");
- }
- return sb.toString();
- }
-
- public void setActivatedStateSupported(boolean flag) {
- this.mActivatedStateSupported = flag;
- }
-
- public void setAdjustSelectionBoundsEnabled(boolean enabled) {
- mAdjustSelectionBoundsEnabled = enabled;
- }
-
- @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();
- }
-
- /**
- * Set drawable resources directly for the drawable resource of the photo view.
- *
- * @param drawable A drawable resource.
- */
- public void setDrawable(Drawable drawable) {
- ImageView photo = getPhotoView();
- photo.setScaleType(ImageView.ScaleType.CENTER);
- int iconColor = ContextCompat.getColor(getContext(), R.color.search_shortcut_icon_color);
- photo.setImageDrawable(drawable);
- photo.setImageTintList(ColorStateList.valueOf(iconColor));
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- final float x = event.getX();
- final float y = event.getY();
- // If the touch event's coordinates are not within the view's header, then delegate
- // to super.onTouchEvent so that regular view behavior is preserved. Otherwise, consume
- // and ignore the touch event.
- if (mBoundsWithoutHeader.contains((int) x, (int) y) || !pointIsInView(x, y)) {
- return super.onTouchEvent(event);
- } else {
- return true;
- }
- }
-
- private boolean pointIsInView(float localX, float localY) {
- return localX >= mLeftOffset
- && localX < mRightOffset
- && localY >= 0
- && localY < (getBottom() - getTop());
- }
-
- /**
- * Where to put contact photo. This affects the other Views' layout or look-and-feel.
- *
- * <p>TODO: replace enum with int constants
- */
- public enum PhotoPosition {
- LEFT,
- RIGHT
- }
-
- protected static class HighlightSequence {
-
- private final int start;
- private final int end;
-
- HighlightSequence(int start, int end) {
- this.start = start;
- this.end = end;
- }
- }
-}
diff --git a/java/com/android/contacts/common/list/ContactListPinnedHeaderView.java b/java/com/android/contacts/common/list/ContactListPinnedHeaderView.java
deleted file mode 100644
index 1f3e2bfe3..000000000
--- a/java/com/android/contacts/common/list/ContactListPinnedHeaderView.java
+++ /dev/null
@@ -1,70 +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.common.list;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.LinearLayout.LayoutParams;
-import android.widget.TextView;
-import com.android.contacts.common.R;
-
-/** A custom view for the pinned section header shown at the top of the contact list. */
-public class ContactListPinnedHeaderView extends TextView {
-
- public ContactListPinnedHeaderView(Context context, AttributeSet attrs, View parent) {
- super(context, attrs);
-
- if (R.styleable.ContactListItemView == null) {
- return;
- }
- TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ContactListItemView);
- int backgroundColor =
- a.getColor(R.styleable.ContactListItemView_list_item_background_color, Color.WHITE);
- int textOffsetTop =
- a.getDimensionPixelSize(R.styleable.ContactListItemView_list_item_text_offset_top, 0);
- int paddingStartOffset =
- a.getDimensionPixelSize(R.styleable.ContactListItemView_list_item_padding_left, 0);
- int textWidth = getResources().getDimensionPixelSize(R.dimen.contact_list_section_header_width);
- int widthIncludingPadding = paddingStartOffset + textWidth;
- a.recycle();
-
- setBackgroundColor(backgroundColor);
- setTextAppearance(getContext(), R.style.SectionHeaderStyle);
- setLayoutParams(new LayoutParams(textWidth, LayoutParams.WRAP_CONTENT));
- setLayoutDirection(parent.getLayoutDirection());
- setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL);
-
- // Apply text top offset. Multiply by two, because we are implementing this by padding for a
- // vertically centered view, rather than adjusting the position directly via a layout.
- setPaddingRelative(
- 0, getPaddingTop() + (textOffsetTop * 2), getPaddingEnd(), getPaddingBottom());
- }
-
- /** Sets section header or makes it invisible if the title is null. */
- public void setSectionHeaderTitle(String title) {
- if (!TextUtils.isEmpty(title)) {
- setText(title);
- } else {
- setVisibility(View.GONE);
- }
- }
-}
diff --git a/java/com/android/contacts/common/list/ContactsSectionIndexer.java b/java/com/android/contacts/common/list/ContactsSectionIndexer.java
deleted file mode 100644
index 3f0f2b7ee..000000000
--- a/java/com/android/contacts/common/list/ContactsSectionIndexer.java
+++ /dev/null
@@ -1,119 +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.common.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 static final String BLANK_HEADER_STRING = " ";
- private String[] mSections;
- private int[] mPositions;
- private int mCount;
-
- /**
- * 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 setProfileAndFavoritesHeader(String header, int numberOfItemsToAdd) {
- 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] + numberOfItemsToAdd;
- }
- mSections = tempSections;
- mPositions = tempPositions;
- mCount = mCount + numberOfItemsToAdd;
- }
- }
-}
diff --git a/java/com/android/contacts/common/list/DefaultContactListAdapter.java b/java/com/android/contacts/common/list/DefaultContactListAdapter.java
deleted file mode 100644
index 7bcae0e0e..000000000
--- a/java/com/android/contacts/common/list/DefaultContactListAdapter.java
+++ /dev/null
@@ -1,216 +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.common.list;
-
-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.SearchSnippets;
-import android.text.TextUtils;
-import android.view.View;
-import com.android.contacts.common.compat.ContactsCompat;
-import com.android.contacts.common.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 DefaultContactListAdapter(Context context) {
- super(context);
- }
-
- @Override
- public void configureLoader(CursorLoader loader, long directoryId) {
- String sortOrder = null;
- 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 {
- final Builder builder = ContactsCompat.getContentUri().buildUpon();
- appendSearchParameters(builder, query, directoryId);
- loader.setUri(builder.build());
- loader.setProjection(getProjection(true));
- }
- } else {
- final ContactListFilter filter = getFilter();
- configureUri(loader, directoryId, filter);
- loader.setProjection(getProjection(false));
- configureSelection(loader, directoryId, filter);
- }
-
- if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
- if (sortOrder == null) {
- sortOrder = Contacts.SORT_KEY_PRIMARY;
- } else {
- sortOrder += ", " + Contacts.SORT_KEY_PRIMARY;
- }
- } else {
- if (sortOrder == null) {
- sortOrder = Contacts.SORT_KEY_ALTERNATIVE;
- } else {
- sortOrder += ", " + Contacts.SORT_KEY_ALTERNATIVE;
- }
- }
- loader.setSortOrder(sortOrder);
- }
-
- private void appendSearchParameters(Builder builder, String query, long directoryId) {
- 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(getDirectoryById(directoryId))));
- }
- builder.appendQueryParameter(SearchSnippets.DEFERRED_SNIPPETING_KEY, "1");
- }
-
- protected void configureUri(CursorLoader loader, long directoryId, ContactListFilter filter) {
- Uri uri = Contacts.CONTENT_URI;
-
- if (directoryId == Directory.DEFAULT && isSectionHeaderDisplayEnabled()) {
- uri = ContactListAdapter.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) {
- super.bindView(itemView, partition, cursor, position);
- final ContactListItemView view = (ContactListItemView) itemView;
-
- view.setHighlightedPrefix(isSearchMode() ? getUpperCaseQueryString() : null);
-
- 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,
- ContactQuery.CONTACT_DISPLAY_NAME);
- } else {
- if (getDisplayPhotos()) {
- bindPhoto(view, partition, cursor);
- }
- }
-
- bindNameAndViewId(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/java/com/android/contacts/common/list/DirectoryListLoader.java b/java/com/android/contacts/common/list/DirectoryListLoader.java
deleted file mode 100644
index ce78d2cff..000000000
--- a/java/com/android/contacts/common/list/DirectoryListLoader.java
+++ /dev/null
@@ -1,210 +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.common.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 com.android.contacts.common.R;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.common.cp2.DirectoryCompat;
-import com.android.dialer.util.PermissionsUtil;
-
-/** A specialized loader for the list of directories, see {@link Directory}. */
-public class DirectoryListLoader extends AsyncTaskLoader<Cursor> {
-
- 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;
- // This is a virtual column created for a MatrixCursor.
- 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() {
- if (PermissionsUtil.hasContactsReadPermissions(getContext())) {
- getContext()
- .getContentResolver()
- .registerContentObserver(DirectoryQuery.URI, false, mObserver);
- } else {
- LogUtil.w("DirectoryListLoader.onStartLoading", "contacts permission not available.");
- }
- 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 = null;
- break;
-
- case SEARCH_MODE_CONTACT_SHORTCUT:
- selection = Directory.SHORTCUT_SUPPORT + "=" + Directory.SHORTCUT_SUPPORT_FULL;
- break;
-
- case SEARCH_MODE_DATA_SHORTCUT:
- selection =
- Directory.SHORTCUT_SUPPORT
- + " IN ("
- + Directory.SHORTCUT_SUPPORT_FULL
- + ", "
- + Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY
- + ")";
- break;
-
- default:
- throw new RuntimeException("Unsupported directory search mode: " + mDirectorySearchMode);
- }
- Cursor cursor = null;
- try {
- cursor =
- context
- .getContentResolver()
- .query(
- DirectoryQuery.URI,
- DirectoryQuery.PROJECTION,
- selection,
- null,
- DirectoryQuery.ORDER_BY);
-
- if (cursor == null) {
- return result;
- }
-
- while (cursor.moveToNext()) {
- long directoryId = cursor.getLong(DirectoryQuery.ID);
- if (!mLocalInvisibleDirectoryEnabled && DirectoryCompat.isInvisibleDirectory(directoryId)) {
- continue;
- }
- 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) {
- LogUtil.e(
- "ContactEntryListAdapter.loadInBackground",
- "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});
- }
- } catch (RuntimeException e) {
- LogUtil.w(
- "ContactEntryListAdapter.loadInBackground", "runtime exception when querying directory");
- } finally {
- if (cursor != null) {
- 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();
- }
-
- private static final class DirectoryQuery {
-
- public static final Uri URI = DirectoryCompat.getContentUri();
- 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;
- }
-}
diff --git a/java/com/android/contacts/common/list/DirectoryPartition.java b/java/com/android/contacts/common/list/DirectoryPartition.java
deleted file mode 100644
index 26b851041..000000000
--- a/java/com/android/contacts/common/list/DirectoryPartition.java
+++ /dev/null
@@ -1,179 +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.common.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;
-
- public static final int RESULT_LIMIT_DEFAULT = -1;
-
- private long mDirectoryId;
- private String mContentUri;
- private String mDirectoryType;
- private String mDisplayName;
- private int mStatus;
- private boolean mPriorityDirectory;
- private boolean mPhotoSupported;
- private int mResultLimit = RESULT_LIMIT_DEFAULT;
- private boolean mDisplayNumber = true;
-
- private String mLabel;
-
- 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;
- }
-
- /**
- * Max number of results for this directory. Defaults to {@link #RESULT_LIMIT_DEFAULT} which
- * implies using the adapter's {@link
- * com.android.contacts.common.list.ContactListAdapter#getDirectoryResultLimit()}
- */
- public int getResultLimit() {
- return mResultLimit;
- }
-
- public void setResultLimit(int resultLimit) {
- mResultLimit = resultLimit;
- }
-
- /**
- * Used by extended directories to specify a custom content URI. Extended directories MUST have a
- * content URI
- */
- public String getContentUri() {
- return mContentUri;
- }
-
- public void setContentUri(String contentUri) {
- mContentUri = contentUri;
- }
-
- /** A label to display in the header next to the display name. */
- public String getLabel() {
- return mLabel;
- }
-
- public void setLabel(String label) {
- mLabel = label;
- }
-
- @Override
- public String toString() {
- return "DirectoryPartition{"
- + "mDirectoryId="
- + mDirectoryId
- + ", mContentUri='"
- + mContentUri
- + '\''
- + ", mDirectoryType='"
- + mDirectoryType
- + '\''
- + ", mDisplayName='"
- + mDisplayName
- + '\''
- + ", mStatus="
- + mStatus
- + ", mPriorityDirectory="
- + mPriorityDirectory
- + ", mPhotoSupported="
- + mPhotoSupported
- + ", mResultLimit="
- + mResultLimit
- + ", mLabel='"
- + mLabel
- + '\''
- + '}';
- }
-
- /**
- * Whether or not to display the phone number in app that have that option - Dialer. If false,
- * Phone Label should be used instead of Phone Number.
- */
- public boolean isDisplayNumber() {
- return mDisplayNumber;
- }
-
- public void setDisplayNumber(boolean displayNumber) {
- mDisplayNumber = displayNumber;
- }
-}
diff --git a/java/com/android/contacts/common/list/IndexerListAdapter.java b/java/com/android/contacts/common/list/IndexerListAdapter.java
deleted file mode 100644
index 2289f6e59..000000000
--- a/java/com/android/contacts/common/list/IndexerListAdapter.java
+++ /dev/null
@@ -1,214 +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.common.list;
-
-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;
- 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);
-
- 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 {
- View topChild = listView.getChildAt(listPosition);
- if (topChild != null) {
- // Match the pinned header's height to the height of the list item.
- mHeader.setMinimumHeight(topChild.getMeasuredHeight());
- }
- setPinnedSectionTitle(mHeader, (String) mIndexer.getSections()[section]);
-
- // 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;
- }
-
- /**
- * 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 {
-
- public boolean firstInSection;
- public boolean lastInSection;
- public String sectionHeader;
- private int position = ListView.INVALID_POSITION;
-
- public void invalidate() {
- position = ListView.INVALID_POSITION;
- }
- }
-}
diff --git a/java/com/android/contacts/common/list/PhoneNumberListAdapter.java b/java/com/android/contacts/common/list/PhoneNumberListAdapter.java
deleted file mode 100644
index 3c45abf37..000000000
--- a/java/com/android/contacts/common/list/PhoneNumberListAdapter.java
+++ /dev/null
@@ -1,656 +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.common.list;
-
-import android.content.Context;
-import android.content.CursorLoader;
-import android.database.Cursor;
-import android.net.Uri;
-import android.net.Uri.Builder;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Callable;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.SipAddress;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Directory;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.ViewGroup;
-import com.android.contacts.common.ContactsUtils;
-import com.android.contacts.common.R;
-import com.android.contacts.common.compat.CallableCompat;
-import com.android.contacts.common.compat.PhoneCompat;
-import com.android.contacts.common.extensions.PhoneDirectoryExtenderAccessor;
-import com.android.contacts.common.list.ContactListItemView.CallToAction;
-import com.android.contacts.common.preference.ContactsPreferences;
-import com.android.contacts.common.util.Constants;
-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.DefaultImageRequest;
-import com.android.dialer.dialercontact.DialerContact;
-import com.android.dialer.duo.DuoComponent;
-import com.android.dialer.enrichedcall.EnrichedCallCapabilities;
-import com.android.dialer.enrichedcall.EnrichedCallComponent;
-import com.android.dialer.enrichedcall.EnrichedCallManager;
-import com.android.dialer.lettertile.LetterTileDrawable;
-import com.android.dialer.phonenumberutil.PhoneNumberHelper;
-import com.android.dialer.util.CallUtil;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * A cursor adapter for the {@link Phone#CONTENT_ITEM_TYPE} and {@link
- * SipAddress#CONTENT_ITEM_TYPE}.
- *
- * <p>By default this adapter just handles phone numbers. When {@link #setUseCallableUri(boolean)}
- * is called with "true", this adapter starts handling SIP addresses too, by using {@link Callable}
- * API instead of {@link Phone}.
- */
-public class PhoneNumberListAdapter extends ContactEntryListAdapter {
-
- private static final String TAG = PhoneNumberListAdapter.class.getSimpleName();
- private static final String IGNORE_NUMBER_TOO_LONG_CLAUSE = "length(" + Phone.NUMBER + ") < 1000";
- // A list of extended directories to add to the directories from the database
- private final List<DirectoryPartition> mExtendedDirectories;
- private final CharSequence mUnknownNameText;
- protected final boolean mIsImsVideoEnabled;
-
- // Extended directories will have ID's that are higher than any of the id's from the database,
- // so that we can identify them and set them up properly. If no extended directories
- // exist, this will be Long.MAX_VALUE
- private long mFirstExtendedDirectoryId = Long.MAX_VALUE;
- private boolean mUseCallableUri;
- private Listener mListener;
-
- public PhoneNumberListAdapter(Context context) {
- super(context);
- setDefaultFilterHeaderText(R.string.list_filter_phones);
- mUnknownNameText = context.getText(android.R.string.unknownName);
-
- mExtendedDirectories =
- PhoneDirectoryExtenderAccessor.get(mContext).getExtendedDirectories(mContext);
-
- int videoCapabilities = CallUtil.getVideoCallingAvailability(context);
- mIsImsVideoEnabled =
- CallUtil.isVideoEnabled(context)
- && (videoCapabilities & CallUtil.VIDEO_CALLING_PRESENCE) != 0;
- }
-
- @Override
- public void configureLoader(CursorLoader loader, long directoryId) {
- String query = getQueryString();
- if (query == null) {
- query = "";
- }
- if (isExtendedDirectory(directoryId)) {
- final DirectoryPartition directory = getExtendedDirectoryFromId(directoryId);
- final String contentUri = directory.getContentUri();
- if (contentUri == null) {
- throw new IllegalStateException("Extended directory must have a content URL: " + directory);
- }
- final Builder builder = Uri.parse(contentUri).buildUpon();
- builder.appendPath(query);
- builder.appendQueryParameter(
- ContactsContract.LIMIT_PARAM_KEY, String.valueOf(getDirectoryResultLimit(directory)));
- loader.setUri(builder.build());
- loader.setProjection(PhoneQuery.PROJECTION_PRIMARY);
- } else {
- final boolean isRemoteDirectoryQuery = DirectoryCompat.isRemoteDirectoryId(directoryId);
- final Builder builder;
- if (isSearchMode()) {
- final Uri baseUri;
- if (isRemoteDirectoryQuery) {
- baseUri = PhoneCompat.getContentFilterUri();
- } else if (mUseCallableUri) {
- baseUri = CallableCompat.getContentFilterUri();
- } else {
- baseUri = PhoneCompat.getContentFilterUri();
- }
- builder = baseUri.buildUpon();
- builder.appendPath(query); // Builder will encode the query
- builder.appendQueryParameter(
- ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId));
- if (isRemoteDirectoryQuery) {
- builder.appendQueryParameter(
- ContactsContract.LIMIT_PARAM_KEY,
- String.valueOf(getDirectoryResultLimit(getDirectoryById(directoryId))));
- }
- } else {
- Uri baseUri = mUseCallableUri ? Callable.CONTENT_URI : Phone.CONTENT_URI;
- builder =
- baseUri
- .buildUpon()
- .appendQueryParameter(
- ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT));
- if (isSectionHeaderDisplayEnabled()) {
- builder.appendQueryParameter(Phone.EXTRA_ADDRESS_BOOK_INDEX, "true");
- }
- applyFilter(loader, builder, directoryId, getFilter());
- }
-
- // Ignore invalid phone numbers that are too long. These can potentially cause freezes
- // in the UI and there is no reason to display them.
- final String prevSelection = loader.getSelection();
- final String newSelection;
- if (!TextUtils.isEmpty(prevSelection)) {
- newSelection = prevSelection + " AND " + IGNORE_NUMBER_TOO_LONG_CLAUSE;
- } else {
- newSelection = IGNORE_NUMBER_TOO_LONG_CLAUSE;
- }
- loader.setSelection(newSelection);
-
- // Remove duplicates when it is possible.
- builder.appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true");
- loader.setUri(builder.build());
-
- // TODO a projection that includes the search snippet
- if (getContactNameDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
- loader.setProjection(PhoneQuery.PROJECTION_PRIMARY);
- } else {
- loader.setProjection(PhoneQuery.PROJECTION_ALTERNATIVE);
- }
-
- if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
- loader.setSortOrder(Phone.SORT_KEY_PRIMARY);
- } else {
- loader.setSortOrder(Phone.SORT_KEY_ALTERNATIVE);
- }
- }
- }
-
- protected boolean isExtendedDirectory(long directoryId) {
- return directoryId >= mFirstExtendedDirectoryId;
- }
-
- private DirectoryPartition getExtendedDirectoryFromId(long directoryId) {
- final int directoryIndex = (int) (directoryId - mFirstExtendedDirectoryId);
- return mExtendedDirectories.get(directoryIndex);
- }
-
- /**
- * Configure {@code loader} and {@code uriBuilder} according to {@code directoryId} and {@code
- * filter}.
- */
- private void applyFilter(
- CursorLoader loader, Uri.Builder uriBuilder, long directoryId, ContactListFilter filter) {
- if (filter == null || directoryId != Directory.DEFAULT) {
- return;
- }
-
- final StringBuilder selection = new StringBuilder();
- final List<String> selectionArgs = new ArrayList<String>();
-
- switch (filter.filterType) {
- case ContactListFilter.FILTER_TYPE_CUSTOM:
- {
- selection.append(Contacts.IN_VISIBLE_GROUP + "=1");
- selection.append(" AND " + Contacts.HAS_PHONE_NUMBER + "=1");
- break;
- }
- case ContactListFilter.FILTER_TYPE_ACCOUNT:
- {
- filter.addAccountQueryParameterToUrl(uriBuilder);
- break;
- }
- case ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS:
- case ContactListFilter.FILTER_TYPE_DEFAULT:
- break; // No selection needed.
- case ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY:
- break; // This adapter is always "phone only", so no selection needed either.
- default:
- LogUtil.w(
- TAG,
- "Unsupported filter type came "
- + "(type: "
- + filter.filterType
- + ", toString: "
- + filter
- + ")"
- + " showing all contacts.");
- // No selection.
- break;
- }
- loader.setSelection(selection.toString());
- loader.setSelectionArgs(selectionArgs.toArray(new String[0]));
- }
-
- public String getPhoneNumber(int position) {
- final Cursor item = (Cursor) getItem(position);
- return item != null ? item.getString(PhoneQuery.PHONE_NUMBER) : null;
- }
-
- /**
- * Retrieves the lookup key for the given cursor position.
- *
- * @param position The cursor position.
- * @return The lookup key.
- */
- public String getLookupKey(int position) {
- final Cursor item = (Cursor) getItem(position);
- return item != null ? item.getString(PhoneQuery.LOOKUP_KEY) : null;
- }
-
- public DialerContact getDialerContact(int position) {
- Cursor cursor = (Cursor) getItem(position);
- if (cursor == null) {
- LogUtil.e("PhoneNumberListAdapter.getDialerContact", "cursor was null.");
- return null;
- }
-
- String displayName = cursor.getString(PhoneQuery.DISPLAY_NAME);
- String number = cursor.getString(PhoneQuery.PHONE_NUMBER);
- String photoUri = cursor.getString(PhoneQuery.PHOTO_URI);
- Uri contactUri =
- Contacts.getLookupUri(
- cursor.getLong(PhoneQuery.CONTACT_ID), cursor.getString(PhoneQuery.LOOKUP_KEY));
-
- DialerContact.Builder contact = DialerContact.newBuilder();
- contact
- .setNumber(number)
- .setPhotoId(cursor.getLong(PhoneQuery.PHOTO_ID))
- .setContactType(LetterTileDrawable.TYPE_DEFAULT)
- .setNameOrNumber(displayName)
- .setNumberLabel(
- Phone.getTypeLabel(
- mContext.getResources(),
- cursor.getInt(PhoneQuery.PHONE_TYPE),
- cursor.getString(PhoneQuery.PHONE_LABEL))
- .toString());
-
- if (photoUri != null) {
- contact.setPhotoUri(photoUri);
- }
-
- if (contactUri != null) {
- contact.setContactUri(contactUri.toString());
- }
-
- if (!TextUtils.isEmpty(displayName)) {
- contact.setDisplayNumber(number);
- }
-
- return contact.build();
- }
-
- @Override
- protected ContactListItemView newView(
- Context context, int partition, Cursor cursor, int position, ViewGroup parent) {
- ContactListItemView view = super.newView(context, partition, cursor, position, parent);
- view.setUnknownNameText(mUnknownNameText);
- view.setQuickContactEnabled(isQuickContactEnabled());
- return view;
- }
-
- protected void setHighlight(ContactListItemView view, Cursor cursor) {
- view.setHighlightedPrefix(isSearchMode() ? getUpperCaseQueryString() : null);
- }
-
- @Override
- protected void bindView(View itemView, int partition, Cursor cursor, int position) {
- super.bindView(itemView, partition, cursor, position);
- ContactListItemView view = (ContactListItemView) itemView;
-
- setHighlight(view, cursor);
-
- // Look at elements before and after this position, checking if contact IDs are same.
- // If they have one same contact ID, it means they can be grouped.
- //
- // In one group, only the first entry will show its photo and its name, and the other
- // entries in the group show just their data (e.g. phone number, email address).
- cursor.moveToPosition(position);
- boolean isFirstEntry = true;
- final long currentContactId = cursor.getLong(PhoneQuery.CONTACT_ID);
- if (cursor.moveToPrevious() && !cursor.isBeforeFirst()) {
- final long previousContactId = cursor.getLong(PhoneQuery.CONTACT_ID);
- if (currentContactId == previousContactId) {
- isFirstEntry = false;
- }
- }
- cursor.moveToPosition(position);
-
- bindViewId(view, cursor, PhoneQuery.PHONE_ID);
-
- bindSectionHeaderAndDivider(view, position);
- if (isFirstEntry) {
- bindName(view, cursor);
- if (isQuickContactEnabled()) {
- bindQuickContact(
- view,
- partition,
- cursor,
- PhoneQuery.PHOTO_ID,
- PhoneQuery.PHOTO_URI,
- PhoneQuery.CONTACT_ID,
- PhoneQuery.LOOKUP_KEY,
- PhoneQuery.DISPLAY_NAME);
- } else {
- if (getDisplayPhotos()) {
- bindPhoto(view, partition, cursor);
- }
- }
- } else {
- unbindName(view);
-
- view.removePhotoView(true, false);
- }
-
- final DirectoryPartition directory = (DirectoryPartition) getPartition(partition);
- // All sections have headers, so scroll position is off by 1.
- position += getPositionForPartition(partition) + 1;
-
- bindPhoneNumber(view, cursor, directory.isDisplayNumber(), position);
- }
-
- @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
- public void bindPhoneNumber(
- ContactListItemView view, Cursor cursor, boolean displayNumber, int position) {
- CharSequence label = null;
- if (displayNumber && !cursor.isNull(PhoneQuery.PHONE_TYPE)) {
- final int type = cursor.getInt(PhoneQuery.PHONE_TYPE);
- final String customLabel = cursor.getString(PhoneQuery.PHONE_LABEL);
-
- // TODO cache
- label = Phone.getTypeLabel(mContext.getResources(), type, customLabel);
- }
- view.setLabel(label);
- final String text;
- String number = cursor.getString(PhoneQuery.PHONE_NUMBER);
- if (displayNumber) {
- text = number;
- } else {
- // Display phone label. If that's null, display geocoded location for the number
- final String phoneLabel = cursor.getString(PhoneQuery.PHONE_LABEL);
- if (phoneLabel != null) {
- text = phoneLabel;
- } else {
- final String phoneNumber = cursor.getString(PhoneQuery.PHONE_NUMBER);
- text =
- PhoneNumberHelper.getGeoDescription(
- mContext,
- phoneNumber,
- PhoneNumberHelper.getCurrentCountryIso(mContext, null /* PhoneAccountHandle */));
- }
- }
- view.setPhoneNumber(text);
-
- @CallToAction int action = ContactListItemView.NONE;
-
- if (CompatUtils.isVideoCompatible()) {
- // Determine if carrier presence indicates the number supports video calling.
- int carrierPresence = cursor.getInt(PhoneQuery.CARRIER_PRESENCE);
- boolean isPresent = (carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) != 0;
-
- boolean showViewIcon = mIsImsVideoEnabled && isPresent;
- if (showViewIcon) {
- action = ContactListItemView.VIDEO;
- }
- }
-
- if (action == ContactListItemView.NONE
- && DuoComponent.get(mContext).getDuo().isReachable(mContext, number)) {
- action = ContactListItemView.DUO;
- }
-
- if (action == ContactListItemView.NONE) {
- EnrichedCallManager manager = EnrichedCallComponent.get(mContext).getEnrichedCallManager();
- EnrichedCallCapabilities capabilities = manager.getCapabilities(number);
- if (capabilities != null && capabilities.isCallComposerCapable()) {
- action = ContactListItemView.CALL_AND_SHARE;
- } else if (capabilities == null
- && getQueryString() != null
- && getQueryString().length() >= 3) {
- manager.requestCapabilities(number);
- }
- }
-
- view.setCallToAction(action, mListener, position);
- }
-
- protected void bindSectionHeaderAndDivider(final ContactListItemView view, int position) {
- if (isSectionHeaderDisplayEnabled()) {
- Placement placement = getItemPlacementInSection(position);
- view.setSectionHeader(placement.firstInSection ? placement.sectionHeader : null);
- } else {
- view.setSectionHeader(null);
- }
- }
-
- protected void bindName(final ContactListItemView view, Cursor cursor) {
- view.showDisplayName(cursor, PhoneQuery.DISPLAY_NAME);
- // Note: we don't show phonetic names any more (see issue 5265330)
- }
-
- protected void unbindName(final ContactListItemView view) {
- view.hideDisplayName();
- }
-
- @Override
- protected void bindWorkProfileIcon(final ContactListItemView view, int partition) {
- final DirectoryPartition directory = (DirectoryPartition) getPartition(partition);
- final long directoryId = directory.getDirectoryId();
- final long userType = ContactsUtils.determineUserType(directoryId, null);
- // Work directory must not be a extended directory. An extended directory is custom
- // directory in the app, but not a directory provided by framework. So it can't be
- // USER_TYPE_WORK.
- view.setWorkProfileIconEnabled(
- !isExtendedDirectory(directoryId) && userType == ContactsUtils.USER_TYPE_WORK);
- }
-
- protected void bindPhoto(final ContactListItemView view, int partitionIndex, Cursor cursor) {
- if (!isPhotoSupported(partitionIndex)) {
- view.removePhotoView();
- return;
- }
-
- long photoId = 0;
- if (!cursor.isNull(PhoneQuery.PHOTO_ID)) {
- photoId = cursor.getLong(PhoneQuery.PHOTO_ID);
- }
-
- if (photoId != 0) {
- getPhotoLoader()
- .loadThumbnail(view.getPhotoView(), photoId, false, getCircularPhotos(), null);
- } else {
- final String photoUriString = cursor.getString(PhoneQuery.PHOTO_URI);
- final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
-
- DefaultImageRequest request = null;
- if (photoUri == null) {
- final String displayName = cursor.getString(PhoneQuery.DISPLAY_NAME);
- final String lookupKey = cursor.getString(PhoneQuery.LOOKUP_KEY);
- request = new DefaultImageRequest(displayName, lookupKey, getCircularPhotos());
- }
- getPhotoLoader()
- .loadDirectoryPhoto(view.getPhotoView(), photoUri, false, getCircularPhotos(), request);
- }
- }
-
- public void setUseCallableUri(boolean useCallableUri) {
- mUseCallableUri = useCallableUri;
- }
-
- /**
- * Override base implementation to inject extended directories between local & remote directories.
- * This is done in the following steps: 1. Call base implementation to add directories from the
- * cursor. 2. Iterate all base directories and establish the following information: a. The highest
- * directory id so that we can assign unused id's to the extended directories. b. The index of the
- * last non-remote directory. This is where we will insert extended directories. 3. Iterate the
- * extended directories and for each one, assign an ID and insert it in the proper location.
- */
- @Override
- public void changeDirectories(Cursor cursor) {
- super.changeDirectories(cursor);
- if (getDirectorySearchMode() == DirectoryListLoader.SEARCH_MODE_NONE) {
- return;
- }
- int numExtendedDirectories = mExtendedDirectories.size();
-
- if (ConfigProviderBindings.get(getContext()).getBoolean("p13n_ranker_should_enable", false)) {
- // Suggested results wasn't formulated as an extended directory, so manually
- // increment the count here when the feature is enabled. Suggestions are
- // only shown when the ranker is enabled.
- numExtendedDirectories++;
- }
-
- if (getPartitionCount() == cursor.getCount() + numExtendedDirectories) {
- // already added all directories;
- return;
- }
-
- mFirstExtendedDirectoryId = Long.MAX_VALUE;
- if (!mExtendedDirectories.isEmpty()) {
- // The Directory.LOCAL_INVISIBLE is not in the cursor but we can't reuse it's
- // "special" ID.
- long maxId = Directory.LOCAL_INVISIBLE;
- int insertIndex = 0;
- for (int i = 0, n = getPartitionCount(); i < n; i++) {
- final DirectoryPartition partition = (DirectoryPartition) getPartition(i);
- final long id = partition.getDirectoryId();
- if (id > maxId) {
- maxId = id;
- }
- if (!DirectoryCompat.isRemoteDirectoryId(id)) {
- // assuming remote directories come after local, we will end up with the index
- // where we should insert extended directories. This also works if there are no
- // remote directories at all.
- insertIndex = i + 1;
- }
- }
- // Extended directories ID's cannot collide with base directories
- mFirstExtendedDirectoryId = maxId + 1;
- for (int i = 0; i < mExtendedDirectories.size(); i++) {
- final long id = mFirstExtendedDirectoryId + i;
- final DirectoryPartition directory = mExtendedDirectories.get(i);
- if (getPartitionByDirectoryId(id) == -1) {
- addPartition(insertIndex, directory);
- directory.setDirectoryId(id);
- }
- }
- }
- }
-
- @Override
- protected Uri getContactUri(
- int partitionIndex, Cursor cursor, int contactIdColumn, int lookUpKeyColumn) {
- final DirectoryPartition directory = (DirectoryPartition) getPartition(partitionIndex);
- final long directoryId = directory.getDirectoryId();
- if (!isExtendedDirectory(directoryId)) {
- return super.getContactUri(partitionIndex, cursor, contactIdColumn, lookUpKeyColumn);
- }
- return Contacts.CONTENT_LOOKUP_URI
- .buildUpon()
- .appendPath(Constants.LOOKUP_URI_ENCODED)
- .appendQueryParameter(Directory.DISPLAY_NAME, directory.getLabel())
- .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId))
- .encodedFragment(cursor.getString(lookUpKeyColumn))
- .build();
- }
-
- public Listener getListener() {
- return mListener;
- }
-
- public void setListener(Listener listener) {
- mListener = listener;
- }
-
- public interface Listener {
-
- void onVideoCallIconClicked(int position);
-
- void onDuoVideoIconClicked(int position);
-
- void onCallAndShareIconClicked(int position);
- }
-
- public static class PhoneQuery {
-
- /**
- * Optional key used as part of a JSON lookup key to specify an analytics category associated
- * with the row.
- */
- public static final String ANALYTICS_CATEGORY = "analytics_category";
-
- /**
- * Optional key used as part of a JSON lookup key to specify an analytics action associated with
- * the row.
- */
- public static final String ANALYTICS_ACTION = "analytics_action";
-
- /**
- * Optional key used as part of a JSON lookup key to specify an analytics value associated with
- * the row.
- */
- public static final String ANALYTICS_VALUE = "analytics_value";
-
- public static final String[] PROJECTION_PRIMARY_INTERNAL =
- new String[] {
- Phone._ID, // 0
- Phone.TYPE, // 1
- Phone.LABEL, // 2
- Phone.NUMBER, // 3
- Phone.CONTACT_ID, // 4
- Phone.LOOKUP_KEY, // 5
- Phone.PHOTO_ID, // 6
- Phone.DISPLAY_NAME_PRIMARY, // 7
- Phone.PHOTO_THUMBNAIL_URI, // 8
- };
-
- public static final String[] PROJECTION_PRIMARY;
- public static final String[] PROJECTION_ALTERNATIVE_INTERNAL =
- new String[] {
- Phone._ID, // 0
- Phone.TYPE, // 1
- Phone.LABEL, // 2
- Phone.NUMBER, // 3
- Phone.CONTACT_ID, // 4
- Phone.LOOKUP_KEY, // 5
- Phone.PHOTO_ID, // 6
- Phone.DISPLAY_NAME_ALTERNATIVE, // 7
- Phone.PHOTO_THUMBNAIL_URI, // 8
- };
- public static final String[] PROJECTION_ALTERNATIVE;
- public static final int PHONE_ID = 0;
- public static final int PHONE_TYPE = 1;
- public static final int PHONE_LABEL = 2;
- public static final int PHONE_NUMBER = 3;
- public static final int CONTACT_ID = 4;
- public static final int LOOKUP_KEY = 5;
- public static final int PHOTO_ID = 6;
- public static final int DISPLAY_NAME = 7;
- public static final int PHOTO_URI = 8;
- public static final int CARRIER_PRESENCE = 9;
-
- static {
- final List<String> projectionList =
- new ArrayList<>(Arrays.asList(PROJECTION_PRIMARY_INTERNAL));
- projectionList.add(Phone.CARRIER_PRESENCE); // 9
- PROJECTION_PRIMARY = projectionList.toArray(new String[projectionList.size()]);
- }
-
- static {
- final List<String> projectionList =
- new ArrayList<>(Arrays.asList(PROJECTION_ALTERNATIVE_INTERNAL));
- projectionList.add(Phone.CARRIER_PRESENCE); // 9
- PROJECTION_ALTERNATIVE = projectionList.toArray(new String[projectionList.size()]);
- }
- }
-}
diff --git a/java/com/android/contacts/common/list/PhoneNumberPickerFragment.java b/java/com/android/contacts/common/list/PhoneNumberPickerFragment.java
deleted file mode 100644
index 1a3b80f31..000000000
--- a/java/com/android/contacts/common/list/PhoneNumberPickerFragment.java
+++ /dev/null
@@ -1,465 +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.common.list;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.Loader;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.annotation.MainThread;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import com.android.contacts.common.R;
-import com.android.contacts.common.list.PhoneNumberListAdapter.Listener;
-import com.android.contacts.common.util.AccountFilterUtil;
-import com.android.dialer.callintent.CallInitiationType;
-import com.android.dialer.callintent.CallInitiationType.Type;
-import com.android.dialer.callintent.CallSpecificAppData;
-import com.android.dialer.common.Assert;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.dialercontact.DialerContact;
-import com.android.dialer.duo.DuoComponent;
-import com.android.dialer.enrichedcall.EnrichedCallComponent;
-import com.android.dialer.enrichedcall.EnrichedCallManager;
-import com.android.dialer.logging.DialerImpression;
-import com.android.dialer.logging.Logger;
-import com.android.dialer.performancereport.PerformanceReport;
-import com.android.dialer.protos.ProtoParsers;
-import java.util.Set;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/** Fragment containing a phone number list for picking. */
-public class PhoneNumberPickerFragment extends ContactEntryListFragment<ContactEntryListAdapter>
- implements PhoneNumberListAdapter.Listener, EnrichedCallManager.CapabilitiesListener {
-
- private static final String KEY_FILTER = "filter";
- private OnPhoneNumberPickerActionListener mListener;
- private ContactListFilter mFilter;
- private View mAccountFilterHeader;
- /**
- * Lives as ListView's header and is shown when {@link #mAccountFilterHeader} is set to View.GONE.
- */
- private View mPaddingView;
- /** true if the loader has started at least once. */
- private boolean mLoaderStarted;
-
- private boolean mUseCallableUri;
-
- private final Set<OnLoadFinishedListener> mLoadFinishedListeners = new ArraySet<>();
-
- private CursorReranker mCursorReranker;
-
- public PhoneNumberPickerFragment() {
- setQuickContactEnabled(false);
- setPhotoLoaderEnabled(true);
- setSectionHeaderDisplayEnabled(false);
- setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
-
- // Show nothing instead of letting caller Activity show something.
- setHasOptionsMenu(true);
- }
-
- /**
- * Handles a click on the video call icon for a row in the list.
- *
- * @param position The position in the list where the click ocurred.
- */
- @Override
- public void onVideoCallIconClicked(int position) {
- Logger.get(getContext()).logImpression(DialerImpression.Type.IMS_VIDEO_REQUESTED_FROM_SEARCH);
- callNumber(position, true /* isVideoCall */);
- }
-
- @Override
- public void onDuoVideoIconClicked(int position) {
- PerformanceReport.stopRecording();
- String phoneNumber = getPhoneNumber(position);
- Intent intent = DuoComponent.get(getContext()).getDuo().getIntent(getContext(), phoneNumber);
- // DialtactsActivity.ACTIVITY_REQUEST_CODE_LIGHTBRINGER
- // Cannot reference because of cyclic dependencies
- Logger.get(getContext())
- .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_SEARCH);
- int dialactsActivityRequestCode = 3;
- getActivity().startActivityForResult(intent, dialactsActivityRequestCode);
- }
-
- @Override
- public void onCallAndShareIconClicked(int position) {
- // Required because of cyclic dependencies of everything depending on contacts/common.
- String componentName = "com.android.dialer.callcomposer.CallComposerActivity";
- Intent intent = new Intent();
- intent.setComponent(new ComponentName(getContext(), componentName));
- DialerContact contact = ((PhoneNumberListAdapter) getAdapter()).getDialerContact(position);
- ProtoParsers.put(intent, "CALL_COMPOSER_CONTACT", contact);
- startActivity(intent);
- }
-
- public void setDirectorySearchEnabled(boolean flag) {
- setDirectorySearchMode(
- flag ? DirectoryListLoader.SEARCH_MODE_DEFAULT : DirectoryListLoader.SEARCH_MODE_NONE);
- }
-
- public void setOnPhoneNumberPickerActionListener(OnPhoneNumberPickerActionListener listener) {
- this.mListener = listener;
- }
-
- public OnPhoneNumberPickerActionListener getOnPhoneNumberPickerListener() {
- return mListener;
- }
-
- @Override
- protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
- super.onCreateView(inflater, container);
-
- View paddingView = inflater.inflate(R.layout.contact_detail_list_padding, null, false);
- mPaddingView = paddingView.findViewById(R.id.contact_detail_list_padding);
- getListView().addHeaderView(paddingView);
-
- mAccountFilterHeader = getView().findViewById(R.id.account_filter_header_container);
- updateFilterHeaderView();
-
- setVisibleScrollbarEnabled(getVisibleScrollbarEnabled());
- }
-
- @Override
- public void onPause() {
- super.onPause();
- EnrichedCallComponent.get(getContext())
- .getEnrichedCallManager()
- .unregisterCapabilitiesListener(this);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- EnrichedCallComponent.get(getContext())
- .getEnrichedCallManager()
- .registerCapabilitiesListener(this);
- }
-
- protected boolean getVisibleScrollbarEnabled() {
- return true;
- }
-
- @Override
- protected void setSearchMode(boolean flag) {
- super.setSearchMode(flag);
- updateFilterHeaderView();
- }
-
- private void updateFilterHeaderView() {
- final ContactListFilter filter = getFilter();
- if (mAccountFilterHeader == null || filter == null) {
- return;
- }
- final boolean shouldShowHeader =
- !isSearchMode()
- && AccountFilterUtil.updateAccountFilterTitleForPhone(
- mAccountFilterHeader, filter, false);
- if (shouldShowHeader) {
- mPaddingView.setVisibility(View.GONE);
- mAccountFilterHeader.setVisibility(View.VISIBLE);
- } else {
- mPaddingView.setVisibility(View.VISIBLE);
- mAccountFilterHeader.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void restoreSavedState(Bundle savedState) {
- super.restoreSavedState(savedState);
-
- if (savedState == null) {
- return;
- }
-
- mFilter = savedState.getParcelable(KEY_FILTER);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putParcelable(KEY_FILTER, mFilter);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- final int itemId = item.getItemId();
- if (itemId == android.R.id.home) { // See ActionBar#setDisplayHomeAsUpEnabled()
- if (mListener != null) {
- mListener.onHomeInActionBarSelected();
- }
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- protected void onItemClick(int position, long id) {
- callNumber(position, false /* isVideoCall */);
- }
-
- /**
- * Initiates a call to the number at the specified position.
- *
- * @param position The position.
- * @param isVideoCall {@code true} if the call should be initiated as a video call, {@code false}
- * otherwise.
- */
- private void callNumber(int position, boolean isVideoCall) {
- final String number = getPhoneNumber(position);
- if (!TextUtils.isEmpty(number)) {
- cacheContactInfo(position);
- CallSpecificAppData callSpecificAppData =
- CallSpecificAppData.newBuilder()
- .setAllowAssistedDialing(true)
- .setCallInitiationType(getCallInitiationType(true /* isRemoteDirectory */))
- .setPositionOfSelectedSearchResult(position)
- .setCharactersInSearchString(getQueryString() == null ? 0 : getQueryString().length())
- .build();
- mListener.onPickPhoneNumber(number, isVideoCall, callSpecificAppData);
- } else {
- LogUtil.i(
- "PhoneNumberPickerFragment.callNumber",
- "item at %d was clicked before adapter is ready, ignoring",
- position);
- }
-
- // Get the lookup key and track any analytics
- final String lookupKey = getLookupKey(position);
- if (!TextUtils.isEmpty(lookupKey)) {
- maybeTrackAnalytics(lookupKey);
- }
- }
-
- protected void cacheContactInfo(int position) {
- // Not implemented. Hook for child classes
- }
-
- protected String getPhoneNumber(int position) {
- final PhoneNumberListAdapter adapter = (PhoneNumberListAdapter) getAdapter();
- return adapter.getPhoneNumber(position);
- }
-
- protected String getLookupKey(int position) {
- final PhoneNumberListAdapter adapter = (PhoneNumberListAdapter) getAdapter();
- return adapter.getLookupKey(position);
- }
-
- @Override
- protected void startLoading() {
- mLoaderStarted = true;
- super.startLoading();
- }
-
- @Override
- @MainThread
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- Assert.isMainThread();
- // TODO(strongarm): define and verify behavior for "Nearby places", corp directories,
- // and dividers listed in UI between these categories
- if (mCursorReranker != null
- && data != null
- && !data.isClosed()
- && data.getCount() > 0
- && loader.getId() == 0) { // only re-rank if a suggestions loader with id of 0.
- data = mCursorReranker.rerankCursor(data);
- }
- super.onLoadFinished(loader, data);
-
- // disable scroll bar if there is no data
- setVisibleScrollbarEnabled(data != null && !data.isClosed() && data.getCount() > 0);
-
- if (data != null) {
- notifyListeners();
- }
- }
-
- /** Ranks cursor data rows and returns reference to new cursor object with reordered data. */
- public interface CursorReranker {
- @MainThread
- Cursor rerankCursor(Cursor data);
- }
-
- @MainThread
- public void setReranker(@Nullable CursorReranker reranker) {
- Assert.isMainThread();
- mCursorReranker = reranker;
- }
-
- /** Listener that is notified when cursor has finished loading data. */
- public interface OnLoadFinishedListener {
- void onLoadFinished();
- }
-
- @MainThread
- public void addOnLoadFinishedListener(OnLoadFinishedListener listener) {
- Assert.isMainThread();
- mLoadFinishedListeners.add(listener);
- }
-
- @MainThread
- public void removeOnLoadFinishedListener(OnLoadFinishedListener listener) {
- Assert.isMainThread();
- mLoadFinishedListeners.remove(listener);
- }
-
- @MainThread
- protected void notifyListeners() {
- Assert.isMainThread();
- for (OnLoadFinishedListener listener : mLoadFinishedListeners) {
- listener.onLoadFinished();
- }
- }
-
- @Override
- public void onCapabilitiesUpdated() {
- if (getAdapter() != null) {
- EnrichedCallManager manager =
- EnrichedCallComponent.get(getContext()).getEnrichedCallManager();
- Listener listener = ((PhoneNumberListAdapter) getAdapter()).getListener();
-
- for (int i = 0; i < getListView().getChildCount(); i++) {
- if (!(getListView().getChildAt(i) instanceof ContactListItemView)) {
- continue;
- }
-
- // Since call and share is the lowest priority call to action, if any others are set,
- // do not reset the call to action. Also do not set the call and share call to action if
- // the number doesn't support call composer.
- ContactListItemView view = (ContactListItemView) getListView().getChildAt(i);
- if (view.getCallToAction() != ContactListItemView.NONE
- || view.getPhoneNumber() == null
- || manager.getCapabilities(view.getPhoneNumber()) == null
- || !manager.getCapabilities(view.getPhoneNumber()).isCallComposerCapable()) {
- continue;
- }
- view.setCallToAction(ContactListItemView.CALL_AND_SHARE, listener, view.getPosition());
- }
- }
- }
-
- @MainThread
- @Override
- public void onDetach() {
- Assert.isMainThread();
- mLoadFinishedListeners.clear();
- super.onDetach();
- }
-
- public void setUseCallableUri(boolean useCallableUri) {
- mUseCallableUri = useCallableUri;
- }
-
- public boolean usesCallableUri() {
- return mUseCallableUri;
- }
-
- @Override
- protected ContactEntryListAdapter createListAdapter() {
- PhoneNumberListAdapter adapter = new PhoneNumberListAdapter(getActivity());
- adapter.setDisplayPhotos(true);
- adapter.setUseCallableUri(mUseCallableUri);
- return adapter;
- }
-
- @Override
- protected void configureAdapter() {
- super.configureAdapter();
-
- final ContactEntryListAdapter adapter = getAdapter();
- if (adapter == null) {
- return;
- }
-
- if (!isSearchMode() && mFilter != null) {
- adapter.setFilter(mFilter);
- }
- }
-
- @Override
- protected View inflateView(LayoutInflater inflater, ViewGroup container) {
- return inflater.inflate(R.layout.contact_list_content, null);
- }
-
- public ContactListFilter getFilter() {
- return mFilter;
- }
-
- public void setFilter(ContactListFilter filter) {
- if ((mFilter == null && filter == null) || (mFilter != null && mFilter.equals(filter))) {
- return;
- }
-
- mFilter = filter;
- if (mLoaderStarted) {
- reloadData();
- }
- updateFilterHeaderView();
- }
-
- /**
- * @param isRemoteDirectory {@code true} if the call was initiated using a contact/phone number
- * not in the local contacts database
- */
- protected CallInitiationType.Type getCallInitiationType(boolean isRemoteDirectory) {
- return Type.UNKNOWN_INITIATION;
- }
-
- /**
- * Where a lookup key contains analytic event information, logs the associated analytics event.
- *
- * @param lookupKey The lookup key JSON object.
- */
- private void maybeTrackAnalytics(String lookupKey) {
- try {
- JSONObject json = new JSONObject(lookupKey);
-
- String analyticsCategory =
- json.getString(PhoneNumberListAdapter.PhoneQuery.ANALYTICS_CATEGORY);
- String analyticsAction = json.getString(PhoneNumberListAdapter.PhoneQuery.ANALYTICS_ACTION);
- String analyticsValue = json.getString(PhoneNumberListAdapter.PhoneQuery.ANALYTICS_VALUE);
-
- if (TextUtils.isEmpty(analyticsCategory)
- || TextUtils.isEmpty(analyticsAction)
- || TextUtils.isEmpty(analyticsValue)) {
- return;
- }
-
- // Assume that the analytic value being tracked could be a float value, but just cast
- // to a long so that the analytic server can handle it.
- long value;
- try {
- float floatValue = Float.parseFloat(analyticsValue);
- value = (long) floatValue;
- } catch (NumberFormatException nfe) {
- return;
- }
-
- Logger.get(getActivity())
- .sendHitEventAnalytics(analyticsCategory, analyticsAction, "" /* label */, value);
- } catch (JSONException e) {
- // Not an error; just a lookup key that doesn't have the right information.
- }
- }
-}
diff --git a/java/com/android/contacts/common/list/PinnedHeaderListAdapter.java b/java/com/android/contacts/common/list/PinnedHeaderListAdapter.java
deleted file mode 100644
index 0bdcef084..000000000
--- a/java/com/android/contacts/common/list/PinnedHeaderListAdapter.java
+++ /dev/null
@@ -1,159 +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.common.list;
-
-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 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 getPinnedPartitionHeadersEnabled()
- && 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));
- view.setLayoutDirection(parent.getLayoutDirection());
- return view;
- } else {
- return null;
- }
- }
-
- @Override
- public void configurePinnedHeaders(PinnedHeaderListView listView) {
- if (!getPinnedPartitionHeadersEnabled()) {
- 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;
-
- listView.setHeaderPinnedAtBottom(i, listHeight - bottomHeaderHeight, false);
- 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/java/com/android/contacts/common/list/PinnedHeaderListView.java b/java/com/android/contacts/common/list/PinnedHeaderListView.java
deleted file mode 100644
index a801624b4..000000000
--- a/java/com/android/contacts/common/list/PinnedHeaderListView.java
+++ /dev/null
@@ -1,563 +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.common.list;
-
-import android.content.Context;
-import android.graphics.Canvas;
-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;
-import com.android.dialer.util.ViewUtil;
-
-/**
- * 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 {
-
- 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 int DEFAULT_SMOOTH_SCROLL_DURATION = 100;
- private PinnedHeaderAdapter mAdapter;
- private int mSize;
- private PinnedHeader[] mHeaders;
- private RectF mBounds = new RectF();
- private OnScrollListener mOnScrollListener;
- private OnItemSelectedListener mOnItemSelectedListener;
- private int mScrollState;
- private boolean mScrollToSectionOnHeaderTouch = false;
- private boolean mHeaderTouched = false;
- private int mAnimationDuration = DEFAULT_ANIMATION_DURATION;
- private boolean mAnimating;
- private long mAnimationTargetTime;
- private int mHeaderPaddingStart;
- private int mHeaderWidth;
-
- public PinnedHeaderListView(Context context) {
- this(context, null, android.R.attr.listViewStyle);
- }
-
- public PinnedHeaderListView(Context context, AttributeSet attrs) {
- this(context, attrs, android.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);
- mHeaderPaddingStart = getPaddingStart();
- mHeaderWidth = r - l - mHeaderPaddingStart - getPaddingEnd();
- }
-
- @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);
- }
-
- public void setScrollToSectionOnHeaderTouch(boolean value) {
- mScrollToSectionOnHeaderTouch = value;
- }
-
- @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()) {
- ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
- int widthSpec;
- int heightSpec;
-
- if (layoutParams != null && layoutParams.width > 0) {
- widthSpec = View.MeasureSpec.makeMeasureSpec(layoutParams.width, View.MeasureSpec.EXACTLY);
- } else {
- widthSpec = View.MeasureSpec.makeMeasureSpec(mHeaderWidth, View.MeasureSpec.EXACTLY);
- }
-
- if (layoutParams != null && layoutParams.height > 0) {
- heightSpec =
- View.MeasureSpec.makeMeasureSpec(layoutParams.height, View.MeasureSpec.EXACTLY);
- } else {
- heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
- }
- view.measure(widthSpec, heightSpec);
- int height = view.getMeasuredHeight();
- mHeaders[viewIndex].height = height;
- view.layout(0, 0, view.getMeasuredWidth(), 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) {
- mHeaderTouched = false;
- if (super.onInterceptTouchEvent(ev)) {
- return true;
- }
-
- if (mScrollState == SCROLL_STATE_IDLE) {
- final int y = (int) ev.getY();
- final int x = (int) ev.getX();
- for (int i = mSize; --i >= 0; ) {
- PinnedHeader header = mHeaders[i];
- // For RTL layouts, this also takes into account that the scrollbar is on the left
- // side.
- final int padding = getPaddingLeft();
- if (header.visible
- && header.y <= y
- && header.y + header.height > y
- && x >= padding
- && padding + header.view.getWidth() >= x) {
- mHeaderTouched = true;
- if (mScrollToSectionOnHeaderTouch && ev.getAction() == MotionEvent.ACTION_DOWN) {
- return smoothScrollToPartition(i);
- } else {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (mHeaderTouched) {
- if (ev.getAction() == MotionEvent.ACTION_UP) {
- mHeaderTouched = false;
- }
- return true;
- }
- return super.onTouchEvent(ev);
- }
-
- private boolean smoothScrollToPartition(int partition) {
- if (mAdapter == null) {
- return false;
- }
- 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, DEFAULT_SMOOTH_SCROLL_DURATION);
- 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();
- }
-
- super.dispatchDraw(canvas);
-
- if (hasVisibleHeaders) {
- canvas.restore();
-
- // If the first item is visible and if it has a positive top that is greater than the
- // first header's assigned y-value, use that for the first header's y value. This way,
- // the header inherits any padding applied to the list view.
- if (mSize > 0 && getFirstVisiblePosition() == 0) {
- View firstChild = getChildAt(0);
- PinnedHeader firstHeader = mHeaders[0];
-
- if (firstHeader != null) {
- int firstHeaderTop = firstChild != null ? firstChild.getTop() : 0;
- firstHeader.y = Math.max(firstHeader.y, firstHeaderTop);
- }
- }
-
- // 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();
- int translateX =
- ViewUtil.isViewLayoutRtl(this)
- ? getWidth() - mHeaderPaddingStart - view.getWidth()
- : mHeaderPaddingStart;
- canvas.translate(translateX, header.y);
- if (header.state == FADING) {
- mBounds.set(0, 0, view.getWidth(), view.getHeight());
- canvas.saveLayerAlpha(mBounds, header.alpha);
- }
- view.draw(canvas);
- canvas.restoreToCount(saveCount);
- }
- }
-
- /** 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 class PinnedHeader {
-
- View view;
- boolean visible;
- int y;
- int height;
- int alpha;
- int state;
-
- boolean animating;
- boolean targetVisible;
- int sourceY;
- int targetY;
- long targetTime;
- }
-}