diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2018-03-27 06:34:19 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2018-03-27 06:34:19 +0000 |
commit | 966800d977660c8ce78d988af5284647d02d6555 (patch) | |
tree | 8588b9122e464ebe64930271c766a3120ee5ac60 /java | |
parent | 0dcbfd3d1ccdde2ba1e3a582c9aac97aa242f972 (diff) | |
parent | 57fdc2b9ab68bff217d4c9c605ef89cefd66f678 (diff) |
Merge changes I1e9b61d7,I2720a252,I1b162f3e
* changes:
Increase minSdk to 24.
Delete old search, old contacts, p13n logger, filtered numbers add number search.
Add learn more info to VM Transcription setting
Diffstat (limited to 'java')
87 files changed, 150 insertions, 9191 deletions
diff --git a/java/com/android/contacts/common/dialog/CallSubjectDialog.java b/java/com/android/contacts/common/dialog/CallSubjectDialog.java index 48f292cfa..bbf31e844 100644 --- a/java/com/android/contacts/common/dialog/CallSubjectDialog.java +++ b/java/com/android/contacts/common/dialog/CallSubjectDialog.java @@ -23,8 +23,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.preference.PreferenceManager; import android.telecom.PhoneAccount; @@ -519,12 +517,6 @@ public class CallSubjectDialog extends Activity { * current phone account. */ private void loadConfiguration() { - // Only attempt to load configuration from the phone account extras if the SDK is N or - // later. If we've got a prior SDK the default encoding and message length will suffice. - if (VERSION.SDK_INT < VERSION_CODES.N) { - return; - } - if (mPhoneAccountHandle == null) { return; } diff --git a/java/com/android/contacts/common/extensions/PhoneDirectoryExtender.java b/java/com/android/contacts/common/extensions/PhoneDirectoryExtender.java index 6c932daf5..5e9753f7e 100644 --- a/java/com/android/contacts/common/extensions/PhoneDirectoryExtender.java +++ b/java/com/android/contacts/common/extensions/PhoneDirectoryExtender.java @@ -17,18 +17,10 @@ package com.android.contacts.common.extensions; import android.content.Context; import android.net.Uri; import android.support.annotation.Nullable; -import com.android.contacts.common.list.DirectoryPartition; -import java.util.List; /** An interface for adding extended phone directories. */ public interface PhoneDirectoryExtender { - /** - * Return a list of extended directories to add. May return null if no directories are to be - * added. - */ - List<DirectoryPartition> getExtendedDirectories(Context context); - /** returns true if the nearby places directory is enabled. */ boolean isEnabled(Context context); diff --git a/java/com/android/contacts/common/extensions/PhoneDirectoryExtenderStub.java b/java/com/android/contacts/common/extensions/PhoneDirectoryExtenderStub.java index 4c3d3d14d..5b3cb9913 100644 --- a/java/com/android/contacts/common/extensions/PhoneDirectoryExtenderStub.java +++ b/java/com/android/contacts/common/extensions/PhoneDirectoryExtenderStub.java @@ -17,19 +17,11 @@ package com.android.contacts.common.extensions; import android.content.Context; import android.net.Uri; import android.support.annotation.Nullable; -import com.android.contacts.common.list.DirectoryPartition; -import java.util.Collections; -import java.util.List; /** No-op implementation for phone directory extender. */ class PhoneDirectoryExtenderStub implements PhoneDirectoryExtender { @Override - public List<DirectoryPartition> getExtendedDirectories(Context context) { - return Collections.emptyList(); - } - - @Override public boolean isEnabled(Context context) { return false; } diff --git a/java/com/android/contacts/common/format/TextHighlighter.java b/java/com/android/contacts/common/format/TextHighlighter.java deleted file mode 100644 index f397f0429..000000000 --- a/java/com/android/contacts/common/format/TextHighlighter.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.contacts.common.format; - -import android.text.SpannableString; -import android.text.style.CharacterStyle; -import android.text.style.StyleSpan; -import android.widget.TextView; - -/** Highlights the text in a text field. */ -public class TextHighlighter { - - private int mTextStyle; - - private CharacterStyle mTextStyleSpan; - - public TextHighlighter(int textStyle) { - mTextStyle = textStyle; - mTextStyleSpan = getStyleSpan(); - } - - /** - * Sets the text on the given text view, highlighting the word that matches the given prefix. - * - * @param view the view on which to set the text - * @param text the string to use as the text - * @param prefix the prefix to look for - */ - public void setPrefixText(TextView view, String text, String prefix) { - view.setText(applyPrefixHighlight(text, prefix)); - } - - private CharacterStyle getStyleSpan() { - return new StyleSpan(mTextStyle); - } - - /** - * Applies highlight span to the text. - * - * @param text Text sequence to be highlighted. - * @param start Start position of the highlight sequence. - * @param end End position of the highlight sequence. - */ - public void applyMaskingHighlight(SpannableString text, int start, int end) { - /** Sets text color of the masked locations to be highlighted. */ - text.setSpan(getStyleSpan(), start, end, 0); - } - - /** - * Returns a CharSequence which highlights the given prefix if found in the given text. - * - * @param text the text to which to apply the highlight - * @param prefix the prefix to look for - */ - public CharSequence applyPrefixHighlight(CharSequence text, String prefix) { - if (prefix == null) { - return text; - } - - // Skip non-word characters at the beginning of prefix. - int prefixStart = 0; - while (prefixStart < prefix.length() - && !Character.isLetterOrDigit(prefix.charAt(prefixStart))) { - prefixStart++; - } - final String trimmedPrefix = prefix.substring(prefixStart); - - int index = indexOfWordPrefix(text, trimmedPrefix); - if (index != -1) { - final SpannableString result = new SpannableString(text); - result.setSpan(mTextStyleSpan, index, index + trimmedPrefix.length(), 0 /* flags */); - return result; - } else { - return text; - } - } - - /** - * Finds the index of the first word that starts with the given prefix. - * - * <p>If not found, returns -1. - * - * @param text the text in which to search for the prefix - * @param prefix the text to find, in upper case letters - */ - public static int indexOfWordPrefix(CharSequence text, String prefix) { - if (prefix == null || text == null) { - return -1; - } - - int textLength = text.length(); - int prefixLength = prefix.length(); - - if (prefixLength == 0 || textLength < prefixLength) { - return -1; - } - - int i = 0; - while (i < textLength) { - // Skip non-word characters - while (i < textLength && !Character.isLetterOrDigit(text.charAt(i))) { - i++; - } - - if (i + prefixLength > textLength) { - return -1; - } - - // Compare the prefixes - int j; - for (j = 0; j < prefixLength; j++) { - if (Character.toUpperCase(text.charAt(i + j)) != prefix.charAt(j)) { - break; - } - } - if (j == prefixLength) { - return i; - } - - // Skip this word - while (i < textLength && Character.isLetterOrDigit(text.charAt(i))) { - i++; - } - } - - return -1; - } -} 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; - } -} diff --git a/java/com/android/dialer/app/AndroidManifest.xml b/java/com/android/dialer/app/AndroidManifest.xml index 0c1a362bb..e77bbe250 100644 --- a/java/com/android/dialer/app/AndroidManifest.xml +++ b/java/com/android/dialer/app/AndroidManifest.xml @@ -55,7 +55,7 @@ <uses-permission android:name="android.permission.STOP_APP_SWITCHES"/> <uses-sdk - android:minSdkVersion="23" + android:minSdkVersion="24" android:targetSdkVersion="27"/> <application android:theme="@style/Theme.AppCompat"> diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java index 1a8e5cc9c..a9a11e008 100644 --- a/java/com/android/dialer/app/DialtactsActivity.java +++ b/java/com/android/dialer/app/DialtactsActivity.java @@ -26,7 +26,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; -import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; @@ -34,7 +33,6 @@ import android.os.Trace; import android.provider.CallLog.Calls; import android.provider.ContactsContract.QuickContact; import android.speech.RecognizerIntent; -import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.support.design.widget.CoordinatorLayout; @@ -66,9 +64,6 @@ import android.widget.TextView; import android.widget.Toast; import com.android.contacts.common.dialog.ClearFrequentsDialog; import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; -import com.android.contacts.common.list.PhoneNumberListAdapter; -import com.android.contacts.common.list.PhoneNumberPickerFragment.CursorReranker; -import com.android.contacts.common.list.PhoneNumberPickerFragment.OnLoadFinishedListener; import com.android.dialer.animation.AnimUtils; import com.android.dialer.animation.AnimationListenerAdapter; import com.android.dialer.app.calllog.CallLogActivity; @@ -84,9 +79,6 @@ import com.android.dialer.app.list.OldSpeedDialFragment; import com.android.dialer.app.list.OnDragDropListener; import com.android.dialer.app.list.OnListFragmentScrolledListener; import com.android.dialer.app.list.PhoneFavoriteSquareTileView; -import com.android.dialer.app.list.RegularSearchFragment; -import com.android.dialer.app.list.SearchFragment; -import com.android.dialer.app.list.SmartDialSearchFragment; import com.android.dialer.app.settings.DialerSettingsActivity; import com.android.dialer.app.widget.ActionBarController; import com.android.dialer.app.widget.SearchEditTextLayout; @@ -120,11 +112,6 @@ import com.android.dialer.logging.ScreenEvent; import com.android.dialer.logging.UiAction; import com.android.dialer.metrics.Metrics; import com.android.dialer.metrics.MetricsComponent; -import com.android.dialer.p13n.inference.P13nRanking; -import com.android.dialer.p13n.inference.protocol.P13nRanker; -import com.android.dialer.p13n.inference.protocol.P13nRanker.P13nRefreshCompleteListener; -import com.android.dialer.p13n.logging.P13nLogger; -import com.android.dialer.p13n.logging.P13nLogging; import com.android.dialer.performancereport.PerformanceReport; import com.android.dialer.postcall.PostCall; import com.android.dialer.precall.PreCall; @@ -161,7 +148,6 @@ public class DialtactsActivity extends TransactionSafeActivity ContactsFragment.OnContactsListScrolledListener, DialpadFragment.HostInterface, OldSpeedDialFragment.HostInterface, - SearchFragment.HostInterface, OnDragDropListener, OnPhoneNumberPickerActionListener, PopupMenu.OnMenuItemClickListener, @@ -192,8 +178,6 @@ public class DialtactsActivity extends TransactionSafeActivity private static final String KEY_IS_DIALPAD_SHOWN = "is_dialpad_shown"; private static final String KEY_FAB_VISIBLE = "fab_visible"; private static final String TAG_NEW_SEARCH_FRAGMENT = "new_search"; - private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search"; - private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial"; private static final String TAG_FAVORITES_FRAGMENT = "favorites"; /** Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}. */ private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER"; @@ -212,11 +196,6 @@ public class DialtactsActivity extends TransactionSafeActivity /** Root layout of DialtactsActivity */ private CoordinatorLayout parentLayout; - /** Fragment for searching phone numbers using the alphanumeric keyboard. */ - private RegularSearchFragment regularSearchFragment; - - /** Fragment for searching phone numbers using the dialpad. */ - private SmartDialSearchFragment smartDialSearchFragment; /** new Fragment for search phone numbers using the keyboard and the dialpad. */ private NewSearchFragment newSearchFragment; @@ -267,8 +246,6 @@ public class DialtactsActivity extends TransactionSafeActivity private boolean wasConfigurationChange; private long timeTabSelected; - private P13nLogger p13nLogger; - private P13nRanker p13nRanker; public boolean isMultiSelectModeEnabled; private boolean isLastTabEnabled; @@ -312,7 +289,6 @@ public class DialtactsActivity extends TransactionSafeActivity LogUtil.v("DialtactsActivity.onTextChanged", "previous query: " + searchQuery); searchQuery = newText; - // TODO(calderwoodra): show p13n when newText is empty. // Show search fragment only when the query string is changed to non-empty text. if (!TextUtils.isEmpty(newText)) { // Call enterSearchUi only if we are switching search modes, or showing a search @@ -324,11 +300,7 @@ public class DialtactsActivity extends TransactionSafeActivity } } - if (smartDialSearchFragment != null && smartDialSearchFragment.isVisible()) { - smartDialSearchFragment.setQueryString(searchQuery); - } else if (regularSearchFragment != null && regularSearchFragment.isVisible()) { - regularSearchFragment.setQueryString(searchQuery); - } else if (newSearchFragment != null && newSearchFragment.isVisible()) { + if (newSearchFragment != null && newSearchFragment.isVisible()) { newSearchFragment.setQuery(searchQuery, getCallInitiationType()); } } @@ -496,14 +468,9 @@ public class DialtactsActivity extends TransactionSafeActivity SmartDialPrefix.initializeNanpSettings(this); Trace.endSection(); - p13nLogger = P13nLogging.get(getApplicationContext()); - p13nRanker = P13nRanking.get(getApplicationContext()); Trace.endSection(); - // Update the new search fragment to the correct position and the ActionBar's visibility. - if (ConfigProviderBindings.get(this).getBoolean("enable_new_search_fragment", false)) { - updateSearchFragmentPosition(); - } + updateSearchFragmentPosition(); } @NonNull @@ -624,14 +591,6 @@ public class DialtactsActivity extends TransactionSafeActivity setSearchBoxHint(); timeTabSelected = SystemClock.elapsedRealtime(); - p13nLogger.reset(); - p13nRanker.refresh( - new P13nRefreshCompleteListener() { - @Override - public void onP13nRefreshComplete() { - // TODO(strongarm): make zero-query search results visible - } - }); Trace.endSection(); } @@ -696,15 +655,6 @@ public class DialtactsActivity extends TransactionSafeActivity LogUtil.i("DialtactsActivity.onAttachFragment", "fragment: %s", fragment); if (fragment instanceof DialpadFragment) { dialpadFragment = (DialpadFragment) fragment; - } else if (fragment instanceof SmartDialSearchFragment) { - smartDialSearchFragment = (SmartDialSearchFragment) fragment; - smartDialSearchFragment.setOnPhoneNumberPickerActionListener(this); - if (!TextUtils.isEmpty(dialpadQuery)) { - smartDialSearchFragment.setAddToContactNumber(dialpadQuery); - } - } else if (fragment instanceof SearchFragment) { - regularSearchFragment = (RegularSearchFragment) fragment; - regularSearchFragment.setOnPhoneNumberPickerActionListener(this); } else if (fragment instanceof ListsFragment) { listsFragment = (ListsFragment) fragment; listsFragment.addOnPageChangeListener(this); @@ -712,28 +662,6 @@ public class DialtactsActivity extends TransactionSafeActivity newSearchFragment = (NewSearchFragment) fragment; updateSearchFragmentPosition(); } - if (fragment instanceof SearchFragment) { - final SearchFragment searchFragment = (SearchFragment) fragment; - searchFragment.setReranker( - new CursorReranker() { - @Override - @MainThread - public Cursor rerankCursor(Cursor data) { - Assert.isMainThread(); - String queryString = searchFragment.getQueryString(); - return p13nRanker.rankCursor(data, queryString == null ? 0 : queryString.length()); - } - }); - searchFragment.addOnLoadFinishedListener( - new OnLoadFinishedListener() { - @Override - public void onLoadFinished() { - p13nLogger.onSearchQuery( - searchFragment.getQueryString(), - (PhoneNumberListAdapter) searchFragment.getAdapter()); - } - }); - } } protected void handleMenuSettings() { @@ -1001,24 +929,7 @@ public class DialtactsActivity extends TransactionSafeActivity } private void updateSearchFragmentPosition() { - SearchFragment fragment = null; - if (smartDialSearchFragment != null) { - fragment = smartDialSearchFragment; - } else if (regularSearchFragment != null) { - fragment = regularSearchFragment; - } - LogUtil.d( - "DialtactsActivity.updateSearchFragmentPosition", - "fragment: %s, isVisible: %b", - fragment, - fragment != null && fragment.isVisible()); - if (fragment != null) { - // We need to force animation here even when fragment is not visible since it might not be - // visible immediately after screen orientation change and dialpad height would not be - // available immediately which is required to update position. By forcing an animation, - // position will be updated after a delay by when the dialpad height would be available. - fragment.updatePosition(true /* animate */); - } else if (newSearchFragment != null) { + if (newSearchFragment != null) { int animationDuration = getResources().getInteger(R.integer.dialpad_slide_in_duration); int actionbarHeight = getResources().getDimensionPixelSize(R.dimen.action_bar_height_large); int shadowHeight = getResources().getDrawable(R.drawable.search_shadow).getIntrinsicHeight(); @@ -1208,29 +1119,9 @@ public class DialtactsActivity extends TransactionSafeActivity return; } - final FragmentTransaction transaction = getFragmentManager().beginTransaction(); - if (inDialpadSearch && smartDialSearchFragment != null) { - transaction.remove(smartDialSearchFragment); - } else if (inRegularSearch && regularSearchFragment != null) { - transaction.remove(regularSearchFragment); - } - - final String tag; - inDialpadSearch = false; - inRegularSearch = false; - inNewSearch = false; - boolean useNewSearch = - ConfigProviderBindings.get(this).getBoolean("enable_new_search_fragment", false); - if (useNewSearch) { - tag = TAG_NEW_SEARCH_FRAGMENT; - inNewSearch = true; - } else if (smartDialSearch) { - tag = TAG_SMARTDIAL_SEARCH_FRAGMENT; - inDialpadSearch = true; - } else { - tag = TAG_REGULAR_SEARCH_FRAGMENT; - inRegularSearch = true; - } + FragmentTransaction transaction = getFragmentManager().beginTransaction(); + String tag = TAG_NEW_SEARCH_FRAGMENT; + inNewSearch = true; floatingActionButtonController.scaleOut(); @@ -1240,59 +1131,23 @@ public class DialtactsActivity extends TransactionSafeActivity transaction.setTransition(FragmentTransaction.TRANSIT_NONE); } - Fragment fragment = getFragmentManager().findFragmentByTag(tag); + NewSearchFragment fragment = (NewSearchFragment) getFragmentManager().findFragmentByTag(tag); if (fragment == null) { - if (useNewSearch) { - fragment = NewSearchFragment.newInstance(!isDialpadShown()); - } else if (smartDialSearch) { - fragment = new SmartDialSearchFragment(); - } else { - fragment = Bindings.getLegacy(this).newRegularSearchFragment(); - ((SearchFragment) fragment) - .setOnTouchListener( - (v, event) -> { - // Show the FAB when the user touches the lists fragment and the soft - // keyboard is hidden. - hideDialpadFragment(true, false); - v.performClick(); - return false; - }); - } + fragment = NewSearchFragment.newInstance(!isDialpadShown()); transaction.add(R.id.dialtacts_frame, fragment, tag); } else { - // TODO(calderwoodra): if this is a transition from dialpad to searchbar, animate fragment - // down, and vice versa. Perhaps just add a coordinator behavior with the search bar. transaction.show(fragment); } // DialtactsActivity will provide the options menu fragment.setHasOptionsMenu(false); - - // Will show empty list if P13nRanker is not enabled. Else, re-ranked list by the ranker. - if (!useNewSearch) { - ((SearchFragment) fragment) - .setShowEmptyListForNullQuery(p13nRanker.shouldShowEmptyListForNullQuery()); - } else { - // TODO(calderwoodra): add p13n ranker to new search. - } - - if (!smartDialSearch && !useNewSearch) { - ((SearchFragment) fragment).setQueryString(query); - } else if (useNewSearch) { - ((NewSearchFragment) fragment).setQuery(query, getCallInitiationType()); - } + fragment.setQuery(query, getCallInitiationType()); transaction.commit(); if (animate) { Assert.isNotNull(listsFragment.getView()).animate().alpha(0).withLayer(); } listsFragment.setUserVisibleHint(false); - - if (smartDialSearch) { - Logger.get(this).logScreenView(ScreenEvent.Type.SMART_DIAL_SEARCH, this); - } else { - Logger.get(this).logScreenView(ScreenEvent.Type.REGULAR_SEARCH, this); - } } /** Hides the search fragment */ @@ -1336,12 +1191,6 @@ public class DialtactsActivity extends TransactionSafeActivity } final FragmentTransaction transaction = getFragmentManager().beginTransaction(); - if (smartDialSearchFragment != null) { - transaction.remove(smartDialSearchFragment); - } - if (regularSearchFragment != null) { - transaction.remove(regularSearchFragment); - } if (newSearchFragment != null) { transaction.remove(newSearchFragment); } @@ -1405,9 +1254,6 @@ public class DialtactsActivity extends TransactionSafeActivity @Override public void onDialpadQueryChanged(String query) { dialpadQuery = query; - if (smartDialSearchFragment != null) { - smartDialSearchFragment.setAddToContactNumber(query); - } if (newSearchFragment != null) { newSearchFragment.setRawNumber(query); } @@ -1443,13 +1289,6 @@ public class DialtactsActivity extends TransactionSafeActivity @Override public boolean onDialpadSpacerTouchWithEmptyQuery() { - if (inDialpadSearch - && smartDialSearchFragment != null - && !smartDialSearchFragment.isShowingPermissionRequest()) { - PerformanceReport.recordClick(UiAction.Type.CLOSE_DIALPAD); - hideDialpadFragment(true /* animate */, true /* clearDialpad */); - return true; - } return false; } @@ -1625,25 +1464,15 @@ public class DialtactsActivity extends TransactionSafeActivity @Override public void onPageScrollStateChanged(int state) {} - @Override public boolean isActionBarShowing() { return actionBarController.isActionBarShowing(); } - @Override public boolean isDialpadShown() { return isDialpadShown; } @Override - public int getDialpadHeight() { - if (dialpadFragment != null) { - return dialpadFragment.getDialpadHeight(); - } - return 0; - } - - @Override public void setActionBarHideOffset(int offset) { getActionBarSafely().setHideOffset(offset); } diff --git a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java index 08f5585b0..b306e756a 100644 --- a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java +++ b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java @@ -34,7 +34,8 @@ import com.android.dialer.common.concurrent.AsyncTaskExecutors; import com.android.dialer.util.PermissionsUtil; import com.android.voicemail.VoicemailClient; -@TargetApi(VERSION_CODES.M) +/** TODO(calderwoodra): documentation */ +@TargetApi(VERSION_CODES.N) public class CallLogAsyncTaskUtil { private static final String TAG = "CallLogAsyncTaskUtil"; @@ -155,6 +156,7 @@ public class CallLogAsyncTaskUtil { UPDATE_DURATION, } + /** TODO(calderwoodra): documentation */ public interface CallLogAsyncTaskListener { void onDeleteVoicemail(); } diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java index 3afb6bb87..ce6e5baf4 100644 --- a/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java +++ b/java/com/android/dialer/app/calllog/CallLogNotificationsQueryHelper.java @@ -51,7 +51,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; /** Helper class operating on call log notifications. */ -@TargetApi(Build.VERSION_CODES.M) +@TargetApi(Build.VERSION_CODES.N) public class CallLogNotificationsQueryHelper { @VisibleForTesting @@ -341,14 +341,14 @@ public class CallLogNotificationsQueryHelper { @Override @Nullable - @TargetApi(Build.VERSION_CODES.M) + @TargetApi(Build.VERSION_CODES.N) public List<NewCall> query(int type) { return query(type, NO_THRESHOLD); } @Override @Nullable - @TargetApi(Build.VERSION_CODES.M) + @TargetApi(Build.VERSION_CODES.N) @SuppressWarnings("MissingPermission") public List<NewCall> query(int type, long thresholdMillis) { if (!PermissionsUtil.hasPermission(context, Manifest.permission.READ_CALL_LOG)) { diff --git a/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java b/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java index 78d307521..cba389cc3 100644 --- a/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java +++ b/java/com/android/dialer/app/calllog/VisualVoicemailNotifier.java @@ -233,9 +233,6 @@ final class VisualVoicemailNotifier { @Nullable private static Uri getVoicemailRingtoneUri( @NonNull Context context, @Nullable PhoneAccountHandle handle) { - if (VERSION.SDK_INT < VERSION_CODES.N) { - return null; - } if (handle == null) { LogUtil.i("VisualVoicemailNotifier.getVoicemailRingtoneUri", "null handle, getting fallback"); handle = getFallbackAccount(context); @@ -251,9 +248,6 @@ final class VisualVoicemailNotifier { private static int getNotificationDefaultFlags( @NonNull Context context, @Nullable PhoneAccountHandle handle) { - if (VERSION.SDK_INT < VERSION_CODES.N) { - return Notification.DEFAULT_ALL; - } if (handle == null) { LogUtil.i( "VisualVoicemailNotifier.getNotificationDefaultFlags", "null handle, getting fallback"); diff --git a/java/com/android/dialer/app/calllog/VoicemailNotificationJobService.java b/java/com/android/dialer/app/calllog/VoicemailNotificationJobService.java index ba61601ae..754ab2727 100644 --- a/java/com/android/dialer/app/calllog/VoicemailNotificationJobService.java +++ b/java/com/android/dialer/app/calllog/VoicemailNotificationJobService.java @@ -22,7 +22,6 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; -import android.os.Build; import android.provider.VoicemailContract; import com.android.dialer.common.LogUtil; import com.android.dialer.constants.ScheduledJobIds; @@ -37,12 +36,8 @@ public class VoicemailNotificationJobService extends JobService { * notification is visible. */ public static void scheduleJob(Context context) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - LogUtil.i("VoicemailNotificationJobService.scheduleJob", "not supported"); - } else { - context.getSystemService(JobScheduler.class).schedule(getJobInfo(context)); - LogUtil.i("VoicemailNotificationJobService.scheduleJob", "job scheduled"); - } + context.getSystemService(JobScheduler.class).schedule(getJobInfo(context)); + LogUtil.i("VoicemailNotificationJobService.scheduleJob", "job scheduled"); } /** diff --git a/java/com/android/dialer/app/filterednumber/BlockedNumbersFragment.java b/java/com/android/dialer/app/filterednumber/BlockedNumbersFragment.java index cae35d5b6..270ec6d03 100644 --- a/java/com/android/dialer/app/filterednumber/BlockedNumbersFragment.java +++ b/java/com/android/dialer/app/filterednumber/BlockedNumbersFragment.java @@ -29,7 +29,6 @@ import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; import android.widget.TextView; import com.android.dialer.app.R; import com.android.dialer.blocking.BlockedNumbersMigrator; @@ -73,13 +72,11 @@ public class BlockedNumbersFragment extends ListFragment getListView().addHeaderView(inflater.inflate(R.layout.blocked_number_header, null)); getListView().addFooterView(inflater.inflate(R.layout.blocked_number_footer, null)); //replace the icon for add number with LetterTileDrawable(), so it will have identical style - ImageView addNumberIcon = (ImageView) getActivity().findViewById(R.id.add_number_icon); LetterTileDrawable drawable = new LetterTileDrawable(getResources()); drawable.setLetter(ADD_BLOCKED_NUMBER_ICON_LETTER); drawable.setColor( ActivityCompat.getColor(getActivity(), R.color.add_blocked_number_icon_color)); drawable.setIsCircular(true); - addNumberIcon.setImageDrawable(drawable); if (adapter == null) { adapter = @@ -97,7 +94,6 @@ public class BlockedNumbersFragment extends ListFragment blockedNumberListDivider = getActivity().findViewById(R.id.blocked_number_list_divider); getListView().findViewById(R.id.import_button).setOnClickListener(this); getListView().findViewById(R.id.view_numbers_button).setOnClickListener(this); - getListView().findViewById(R.id.add_number_linear_layout).setOnClickListener(this); footerText = (TextView) getActivity().findViewById(R.id.blocked_number_footer_textview); voicemailEnabledChecker = new VisualVoicemailEnabledChecker(getContext(), this); @@ -137,8 +133,6 @@ public class BlockedNumbersFragment extends ListFragment if (FilteredNumberCompat.canUseNewFiltering()) { migratePromoView.setVisibility(View.VISIBLE); blockedNumbersText.setVisibility(View.GONE); - getListView().findViewById(R.id.add_number_linear_layout).setVisibility(View.GONE); - getListView().findViewById(R.id.add_number_linear_layout).setOnClickListener(null); blockedNumberListDivider.setVisibility(View.GONE); importSettings.setVisibility(View.GONE); getListView().findViewById(R.id.import_button).setOnClickListener(null); @@ -218,9 +212,7 @@ public class BlockedNumbersFragment extends ListFragment } int resId = view.getId(); - if (resId == R.id.add_number_linear_layout) { - activity.showSearchUi(); - } else if (resId == R.id.view_numbers_button) { + if (resId == R.id.view_numbers_button) { activity.showNumbersToImportPreviewUi(); } else if (resId == R.id.import_button) { FilteredNumbersUtil.importSendToVoicemailContacts( diff --git a/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java b/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java index 858d28355..5475b4ea3 100644 --- a/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java +++ b/java/com/android/dialer/app/filterednumber/BlockedNumbersSettingsActivity.java @@ -19,17 +19,13 @@ import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.MenuItem; import com.android.dialer.app.R; -import com.android.dialer.app.list.BlockedListSearchFragment; -import com.android.dialer.app.list.SearchFragment; import com.android.dialer.logging.Logger; import com.android.dialer.logging.ScreenEvent; /** TODO(calderwoodra): documentation */ -public class BlockedNumbersSettingsActivity extends AppCompatActivity - implements SearchFragment.HostInterface { +public class BlockedNumbersSettingsActivity extends AppCompatActivity { private static final String TAG_BLOCKED_MANAGEMENT_FRAGMENT = "blocked_management"; - private static final String TAG_BLOCKED_SEARCH_FRAGMENT = "blocked_search"; private static final String TAG_VIEW_NUMBERS_TO_IMPORT_FRAGMENT = "view_numbers_to_import"; @Override @@ -60,27 +56,6 @@ public class BlockedNumbersSettingsActivity extends AppCompatActivity Logger.get(this).logScreenView(ScreenEvent.Type.BLOCKED_NUMBER_MANAGEMENT, this); } - /** Shows fragment with search UI for browsing/finding numbers to block. */ - public void showSearchUi() { - BlockedListSearchFragment fragment = - (BlockedListSearchFragment) - getFragmentManager().findFragmentByTag(TAG_BLOCKED_SEARCH_FRAGMENT); - if (fragment == null) { - fragment = new BlockedListSearchFragment(); - fragment.setHasOptionsMenu(false); - fragment.setShowEmptyListForNullQuery(true); - fragment.setDirectorySearchEnabled(false); - } - - getFragmentManager() - .beginTransaction() - .replace(R.id.blocked_numbers_activity_container, fragment, TAG_BLOCKED_SEARCH_FRAGMENT) - .addToBackStack(null) - .commit(); - - Logger.get(this).logScreenView(ScreenEvent.Type.BLOCKED_NUMBER_ADD_NUMBER, this); - } - /** * Shows fragment with UI to preview the numbers of contacts currently marked as send-to-voicemail * in Contacts. These numbers can be imported into Dialer's blocked number list. @@ -119,24 +94,4 @@ public class BlockedNumbersSettingsActivity extends AppCompatActivity super.onBackPressed(); } } - - @Override - public boolean isActionBarShowing() { - return false; - } - - @Override - public boolean isDialpadShown() { - return false; - } - - @Override - public int getDialpadHeight() { - return 0; - } - - @Override - public int getActionBarHeight() { - return 0; - } } diff --git a/java/com/android/dialer/app/legacybindings/DialerLegacyBindings.java b/java/com/android/dialer/app/legacybindings/DialerLegacyBindings.java index a483af9e9..6eaa2b6b9 100644 --- a/java/com/android/dialer/app/legacybindings/DialerLegacyBindings.java +++ b/java/com/android/dialer/app/legacybindings/DialerLegacyBindings.java @@ -22,7 +22,6 @@ import android.view.ViewGroup; import com.android.dialer.app.calllog.CallLogAdapter; import com.android.dialer.app.calllog.calllogcache.CallLogCache; import com.android.dialer.app.contactinfo.ContactInfoCache; -import com.android.dialer.app.list.RegularSearchFragment; import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter; import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; @@ -47,6 +46,4 @@ public interface DialerLegacyBindings { VoicemailPlaybackPresenter voicemailPlaybackPresenter, @NonNull FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler, int activityType); - - RegularSearchFragment newRegularSearchFragment(); } diff --git a/java/com/android/dialer/app/legacybindings/DialerLegacyBindingsStub.java b/java/com/android/dialer/app/legacybindings/DialerLegacyBindingsStub.java index 488fbad68..e95c4709d 100644 --- a/java/com/android/dialer/app/legacybindings/DialerLegacyBindingsStub.java +++ b/java/com/android/dialer/app/legacybindings/DialerLegacyBindingsStub.java @@ -22,7 +22,6 @@ import android.view.ViewGroup; import com.android.dialer.app.calllog.CallLogAdapter; import com.android.dialer.app.calllog.calllogcache.CallLogCache; import com.android.dialer.app.contactinfo.ContactInfoCache; -import com.android.dialer.app.list.RegularSearchFragment; import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter; import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; @@ -53,9 +52,4 @@ public class DialerLegacyBindingsStub implements DialerLegacyBindings { filteredNumberAsyncQueryHandler, activityType); } - - @Override - public RegularSearchFragment newRegularSearchFragment() { - return new RegularSearchFragment(); - } } diff --git a/java/com/android/dialer/app/list/AllContactsFragment.java b/java/com/android/dialer/app/list/AllContactsFragment.java deleted file mode 100644 index 5076fd9cf..000000000 --- a/java/com/android/dialer/app/list/AllContactsFragment.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2013 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.dialer.app.list; - -import static android.Manifest.permission.READ_CONTACTS; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.Loader; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.QuickContact; -import android.support.v13.app.FragmentCompat; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import com.android.contacts.common.list.ContactEntryListAdapter; -import com.android.contacts.common.list.ContactEntryListFragment; -import com.android.contacts.common.list.ContactListFilter; -import com.android.contacts.common.list.DefaultContactListAdapter; -import com.android.dialer.app.R; -import com.android.dialer.common.LogUtil; -import com.android.dialer.compat.CompatUtils; -import com.android.dialer.logging.InteractionEvent; -import com.android.dialer.logging.Logger; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.util.IntentUtil; -import com.android.dialer.util.PermissionsUtil; -import com.android.dialer.widget.EmptyContentView; -import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener; -import java.util.Arrays; - -/** Fragments to show all contacts with phone numbers. */ -public class AllContactsFragment extends ContactEntryListFragment<ContactEntryListAdapter> - implements OnEmptyViewActionButtonClickedListener, - FragmentCompat.OnRequestPermissionsResultCallback { - - private static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1; - - private EmptyContentView emptyListView; - - /** - * Listen to broadcast events about permissions in order to be notified if the READ_CONTACTS - * permission is granted via the UI in another fragment. - */ - private BroadcastReceiver readContactsPermissionGrantedReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - reloadData(); - } - }; - - public AllContactsFragment() { - setQuickContactEnabled(false); - setAdjustSelectionBoundsEnabled(true); - setPhotoLoaderEnabled(true); - setSectionHeaderDisplayEnabled(true); - setDarkTheme(false); - setVisibleScrollbarEnabled(true); - } - - @Override - public void onViewCreated(View view, android.os.Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - emptyListView = (EmptyContentView) view.findViewById(R.id.empty_list_view); - emptyListView.setImage(R.drawable.empty_contacts); - emptyListView.setDescription(R.string.all_contacts_empty); - emptyListView.setActionClickedListener(this); - getListView().setEmptyView(emptyListView); - emptyListView.setVisibility(View.GONE); - } - - @Override - public void onStart() { - super.onStart(); - PermissionsUtil.registerPermissionReceiver( - getActivity(), readContactsPermissionGrantedReceiver, READ_CONTACTS); - } - - @Override - public void onStop() { - PermissionsUtil.unregisterPermissionReceiver( - getActivity(), readContactsPermissionGrantedReceiver); - super.onStop(); - } - - @Override - protected void startLoading() { - if (PermissionsUtil.hasPermission(getActivity(), READ_CONTACTS)) { - super.startLoading(); - emptyListView.setDescription(R.string.all_contacts_empty); - emptyListView.setActionLabel(R.string.all_contacts_empty_add_contact_action); - } else { - emptyListView.setDescription(R.string.permission_no_contacts); - emptyListView.setActionLabel(R.string.permission_single_turn_on); - emptyListView.setVisibility(View.VISIBLE); - } - } - - @Override - public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - super.onLoadFinished(loader, data); - - if (data == null || data.getCount() == 0) { - emptyListView.setVisibility(View.VISIBLE); - } - } - - @Override - protected ContactEntryListAdapter createListAdapter() { - final DefaultContactListAdapter adapter = - new DefaultContactListAdapter(getActivity()) { - @Override - protected void bindView(View itemView, int partition, Cursor cursor, int position) { - super.bindView(itemView, partition, cursor, position); - itemView.setTag(this.getContactUri(partition, cursor)); - } - }; - adapter.setDisplayPhotos(true); - adapter.setFilter( - ContactListFilter.createFilterWithType(ContactListFilter.FILTER_TYPE_DEFAULT)); - adapter.setSectionHeaderDisplayEnabled(isSectionHeaderDisplayEnabled()); - return adapter; - } - - @Override - protected View inflateView(LayoutInflater inflater, ViewGroup container) { - return inflater.inflate(R.layout.all_contacts_fragment, null); - } - - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - final Uri uri = (Uri) view.getTag(); - if (uri != null) { - Logger.get(getContext()) - .logInteraction(InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_ALL_CONTACTS_GENERAL); - if (CompatUtils.hasPrioritizedMimeType()) { - QuickContact.showQuickContact(getContext(), view, uri, null, Phone.CONTENT_ITEM_TYPE); - } else { - QuickContact.showQuickContact(getActivity(), view, uri, QuickContact.MODE_LARGE, null); - } - } - } - - @Override - protected void onItemClick(int position, long id) { - // Do nothing. Implemented to satisfy ContactEntryListFragment. - } - - @Override - public void onEmptyViewActionButtonClicked() { - final Activity activity = getActivity(); - if (activity == null) { - return; - } - - String[] deniedPermissions = - PermissionsUtil.getPermissionsCurrentlyDenied( - getContext(), PermissionsUtil.allContactsGroupPermissionsUsedInDialer); - if (deniedPermissions.length > 0) { - LogUtil.i( - "AllContactsFragment.onEmptyViewActionButtonClicked", - "Requesting permissions: " + Arrays.toString(deniedPermissions)); - FragmentCompat.requestPermissions( - this, deniedPermissions, READ_CONTACTS_PERMISSION_REQUEST_CODE); - } else { - // Add new contact - DialerUtils.startActivityWithErrorToast( - activity, IntentUtil.getNewContactIntent(), R.string.add_contact_not_available); - } - } - - @Override - public void onRequestPermissionsResult( - int requestCode, String[] permissions, int[] grantResults) { - if (requestCode == READ_CONTACTS_PERMISSION_REQUEST_CODE) { - if (grantResults.length >= 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) { - // Force a refresh of the data since we were missing the permission before this. - reloadData(); - } - } - } -} diff --git a/java/com/android/dialer/app/list/BlockedListSearchAdapter.java b/java/com/android/dialer/app/list/BlockedListSearchAdapter.java deleted file mode 100644 index 575d6e63f..000000000 --- a/java/com/android/dialer/app/list/BlockedListSearchAdapter.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2015 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.dialer.app.list; - -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.view.View; -import com.android.contacts.common.list.ContactListItemView; -import com.android.dialer.app.R; -import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; -import com.android.dialer.location.GeoUtil; - -/** List adapter to display search results for adding a blocked number. */ -public class BlockedListSearchAdapter extends RegularSearchListAdapter { - - private Resources resources; - private FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler; - - public BlockedListSearchAdapter(Context context) { - super(context); - resources = context.getResources(); - disableAllShortcuts(); - setShortcutEnabled(SHORTCUT_BLOCK_NUMBER, true); - - filteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler(context); - } - - @Override - protected boolean isChanged(boolean showNumberShortcuts) { - return setShortcutEnabled(SHORTCUT_BLOCK_NUMBER, showNumberShortcuts || isQuerySipAddress); - } - - public void setViewBlocked(ContactListItemView view, Integer id) { - view.setTag(R.id.block_id, id); - final int textColor = resources.getColor(R.color.blocked_number_block_color); - view.getDataView().setTextColor(textColor); - view.getLabelView().setTextColor(textColor); - //TODO: Add icon - } - - public void setViewUnblocked(ContactListItemView view) { - view.setTag(R.id.block_id, null); - final int textColor = resources.getColor(R.color.dialer_secondary_text_color); - view.getDataView().setTextColor(textColor); - view.getLabelView().setTextColor(textColor); - //TODO: Remove icon - } - - @Override - protected void bindView(View itemView, int partition, Cursor cursor, int position) { - super.bindView(itemView, partition, cursor, position); - - final ContactListItemView view = (ContactListItemView) itemView; - // Reset view state to unblocked. - setViewUnblocked(view); - - final String number = getPhoneNumber(position); - final String countryIso = GeoUtil.getCurrentCountryIso(mContext); - final FilteredNumberAsyncQueryHandler.OnCheckBlockedListener onCheckListener = - new FilteredNumberAsyncQueryHandler.OnCheckBlockedListener() { - @Override - public void onCheckComplete(Integer id) { - if (id != null && id != FilteredNumberAsyncQueryHandler.INVALID_ID) { - setViewBlocked(view, id); - } - } - }; - filteredNumberAsyncQueryHandler.isBlockedNumber(onCheckListener, number, countryIso); - } -} diff --git a/java/com/android/dialer/app/list/BlockedListSearchFragment.java b/java/com/android/dialer/app/list/BlockedListSearchFragment.java deleted file mode 100644 index ce812af6e..000000000 --- a/java/com/android/dialer/app/list/BlockedListSearchFragment.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2015 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.dialer.app.list; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.TypedValue; -import android.view.View; -import android.widget.AdapterView; -import android.widget.EditText; -import android.widget.Toast; -import com.android.contacts.common.list.ContactEntryListAdapter; -import com.android.contacts.common.util.ContactDisplayUtils; -import com.android.dialer.app.R; -import com.android.dialer.app.widget.SearchEditTextLayout; -import com.android.dialer.blocking.BlockNumberDialogFragment; -import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; -import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener; -import com.android.dialer.common.LogUtil; -import com.android.dialer.location.GeoUtil; -import com.android.dialer.logging.InteractionEvent; -import com.android.dialer.logging.Logger; -import com.android.dialer.phonenumberutil.PhoneNumberHelper; - -/** TODO(calderwoodra): documentation */ -public class BlockedListSearchFragment extends RegularSearchFragment - implements BlockNumberDialogFragment.Callback { - - private final TextWatcher phoneSearchQueryTextListener = - new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - setQueryString(s.toString()); - } - - @Override - public void afterTextChanged(Editable s) {} - }; - private final SearchEditTextLayout.Callback searchLayoutCallback = - new SearchEditTextLayout.Callback() { - @Override - public void onBackButtonClicked() { - getActivity().onBackPressed(); - } - - @Override - public void onSearchViewClicked() {} - }; - private FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler; - private EditText searchView; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setShowEmptyListForNullQuery(true); - /* - * Pass in the empty string here so ContactEntryListFragment#setQueryString interprets it as - * an empty search query, rather than as an uninitalized value. In the latter case, the - * adapter returned by #createListAdapter is used, which populates the view with contacts. - * Passing in the empty string forces ContactEntryListFragment to interpret it as an empty - * query, which results in showing an empty view - */ - setQueryString(getQueryString() == null ? "" : getQueryString()); - filteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler(getContext()); - } - - @Override - public void onResume() { - super.onResume(); - - ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); - actionBar.setCustomView(R.layout.search_edittext); - actionBar.setDisplayShowCustomEnabled(true); - actionBar.setDisplayHomeAsUpEnabled(false); - actionBar.setDisplayShowHomeEnabled(false); - - final SearchEditTextLayout searchEditTextLayout = - (SearchEditTextLayout) actionBar.getCustomView().findViewById(R.id.search_view_container); - searchEditTextLayout.expand(false, true); - searchEditTextLayout.setCallback(searchLayoutCallback); - searchEditTextLayout.setBackgroundDrawable(null); - - searchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view); - searchView.addTextChangedListener(phoneSearchQueryTextListener); - searchView.setHint(R.string.block_number_search_hint); - - searchEditTextLayout - .findViewById(R.id.search_box_expanded) - .setBackgroundColor(getContext().getResources().getColor(android.R.color.white)); - - if (!TextUtils.isEmpty(getQueryString())) { - searchView.setText(getQueryString()); - } - - // TODO: Don't set custom text size; use default search text size. - searchView.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - getResources().getDimension(R.dimen.blocked_number_search_text_size)); - } - - @Override - protected ContactEntryListAdapter createListAdapter() { - BlockedListSearchAdapter adapter = new BlockedListSearchAdapter(getActivity()); - adapter.setDisplayPhotos(true); - // Don't show SIP addresses. - adapter.setUseCallableUri(false); - // Keep in sync with the queryString set in #onCreate - adapter.setQueryString(getQueryString() == null ? "" : getQueryString()); - return adapter; - } - - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - super.onItemClick(parent, view, position, id); - final int adapterPosition = position - getListView().getHeaderViewsCount(); - final BlockedListSearchAdapter adapter = (BlockedListSearchAdapter) getAdapter(); - final int shortcutType = adapter.getShortcutTypeFromPosition(adapterPosition); - final Integer blockId = (Integer) view.getTag(R.id.block_id); - final String number; - switch (shortcutType) { - case DialerPhoneNumberListAdapter.SHORTCUT_INVALID: - // Handles click on a search result, either contact or nearby places result. - number = adapter.getPhoneNumber(adapterPosition); - blockContactNumber(number, blockId); - break; - case DialerPhoneNumberListAdapter.SHORTCUT_BLOCK_NUMBER: - // Handles click on 'Block number' shortcut to add the user query as a number. - number = adapter.getQueryString(); - blockNumber(number); - break; - default: - LogUtil.w( - "BlockedListSearchFragment.onItemClick", - "ignoring unsupported shortcut type: " + shortcutType); - break; - } - } - - @Override - protected void onItemClick(int position, long id) { - // Prevent SearchFragment.onItemClicked from being called. - } - - private void blockNumber(final String number) { - final String countryIso = GeoUtil.getCurrentCountryIso(getContext()); - final OnCheckBlockedListener onCheckListener = - new OnCheckBlockedListener() { - @Override - public void onCheckComplete(Integer id) { - if (id == null) { - BlockNumberDialogFragment.show( - id, - number, - countryIso, - PhoneNumberHelper.formatNumber(getContext(), number, countryIso), - R.id.blocked_numbers_activity_container, - getFragmentManager(), - BlockedListSearchFragment.this); - } else if (id == FilteredNumberAsyncQueryHandler.INVALID_ID) { - Toast.makeText( - getContext(), - ContactDisplayUtils.getTtsSpannedPhoneNumber( - getResources(), R.string.invalidNumber, number), - Toast.LENGTH_SHORT) - .show(); - } else { - Toast.makeText( - getContext(), - ContactDisplayUtils.getTtsSpannedPhoneNumber( - getResources(), R.string.alreadyBlocked, number), - Toast.LENGTH_SHORT) - .show(); - } - } - }; - filteredNumberAsyncQueryHandler.isBlockedNumber(onCheckListener, number, countryIso); - } - - @Override - public void onFilterNumberSuccess() { - Logger.get(getContext()).logInteraction(InteractionEvent.Type.BLOCK_NUMBER_MANAGEMENT_SCREEN); - goBack(); - } - - @Override - public void onUnfilterNumberSuccess() { - LogUtil.e( - "BlockedListSearchFragment.onUnfilterNumberSuccess", - "unblocked a number from the BlockedListSearchFragment"); - goBack(); - } - - private void goBack() { - Activity activity = getActivity(); - if (activity == null) { - return; - } - activity.onBackPressed(); - } - - @Override - public void onChangeFilteredNumberUndo() { - getAdapter().notifyDataSetChanged(); - } - - private void blockContactNumber(final String number, final Integer blockId) { - if (blockId != null) { - Toast.makeText( - getContext(), - ContactDisplayUtils.getTtsSpannedPhoneNumber( - getResources(), R.string.alreadyBlocked, number), - Toast.LENGTH_SHORT) - .show(); - return; - } - - BlockNumberDialogFragment.show( - blockId, - number, - GeoUtil.getCurrentCountryIso(getContext()), - number, - R.id.blocked_numbers_activity_container, - getFragmentManager(), - this); - } -} diff --git a/java/com/android/dialer/app/list/DialerPhoneNumberListAdapter.java b/java/com/android/dialer/app/list/DialerPhoneNumberListAdapter.java deleted file mode 100644 index d5609b856..000000000 --- a/java/com/android/dialer/app/list/DialerPhoneNumberListAdapter.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2016 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.dialer.app.list; - -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.drawable.Drawable; -import android.support.v4.content.ContextCompat; -import android.telephony.PhoneNumberUtils; -import android.text.BidiFormatter; -import android.text.TextDirectionHeuristics; -import android.view.View; -import android.view.ViewGroup; -import com.android.contacts.common.list.ContactListItemView; -import com.android.contacts.common.list.PhoneNumberListAdapter; -import com.android.contacts.common.util.ContactDisplayUtils; -import com.android.dialer.app.R; -import com.android.dialer.location.GeoUtil; -import com.android.dialer.phonenumberutil.PhoneNumberHelper; - -/** - * {@link PhoneNumberListAdapter} with the following added shortcuts, that are displayed as list - * items: 1) Directly calling the phone number query 2) Adding the phone number query to a contact - * - * <p>These shortcuts can be enabled or disabled to toggle whether or not they show up in the list. - */ -public class DialerPhoneNumberListAdapter extends PhoneNumberListAdapter { - - public static final int SHORTCUT_INVALID = -1; - public static final int SHORTCUT_DIRECT_CALL = 0; - public static final int SHORTCUT_CREATE_NEW_CONTACT = 1; - public static final int SHORTCUT_ADD_TO_EXISTING_CONTACT = 2; - public static final int SHORTCUT_SEND_SMS_MESSAGE = 3; - public static final int SHORTCUT_MAKE_VIDEO_CALL = 4; - public static final int SHORTCUT_BLOCK_NUMBER = 5; - public static final int SHORTCUT_COUNT = 6; - - private final boolean[] shortcutEnabled = new boolean[SHORTCUT_COUNT]; - private final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); - private final String countryIso; - - private String formattedQueryString; - - public DialerPhoneNumberListAdapter(Context context) { - super(context); - - countryIso = GeoUtil.getCurrentCountryIso(context); - } - - @Override - public int getCount() { - return super.getCount() + getShortcutCount(); - } - - /** @return The number of enabled shortcuts. Ranges from 0 to a maximum of SHORTCUT_COUNT */ - public int getShortcutCount() { - int count = 0; - for (int i = 0; i < shortcutEnabled.length; i++) { - if (shortcutEnabled[i]) { - count++; - } - } - return count; - } - - public void disableAllShortcuts() { - for (int i = 0; i < shortcutEnabled.length; i++) { - shortcutEnabled[i] = false; - } - } - - @Override - public int getItemViewType(int position) { - final int shortcut = getShortcutTypeFromPosition(position); - if (shortcut >= 0) { - // shortcutPos should always range from 1 to SHORTCUT_COUNT - return super.getViewTypeCount() + shortcut; - } else { - return super.getItemViewType(position); - } - } - - @Override - public int getViewTypeCount() { - // Number of item view types in the super implementation + 2 for the 2 new shortcuts - return super.getViewTypeCount() + SHORTCUT_COUNT; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final int shortcutType = getShortcutTypeFromPosition(position); - if (shortcutType >= 0) { - if (convertView != null) { - assignShortcutToView((ContactListItemView) convertView, shortcutType); - return convertView; - } else { - final ContactListItemView v = - new ContactListItemView(getContext(), null, mIsImsVideoEnabled); - assignShortcutToView(v, shortcutType); - return v; - } - } else { - return super.getView(position, convertView, parent); - } - } - - @Override - protected ContactListItemView newView( - Context context, int partition, Cursor cursor, int position, ViewGroup parent) { - final ContactListItemView view = super.newView(context, partition, cursor, position, parent); - - view.setSupportVideoCallIcon(mIsImsVideoEnabled); - return view; - } - - /** - * @param position The position of the item - * @return The enabled shortcut type matching the given position if the item is a shortcut, -1 - * otherwise - */ - public int getShortcutTypeFromPosition(int position) { - int shortcutCount = position - super.getCount(); - if (shortcutCount >= 0) { - // Iterate through the array of shortcuts, looking only for shortcuts where - // mShortcutEnabled[i] is true - for (int i = 0; shortcutCount >= 0 && i < shortcutEnabled.length; i++) { - if (shortcutEnabled[i]) { - shortcutCount--; - if (shortcutCount < 0) { - return i; - } - } - } - throw new IllegalArgumentException( - "Invalid position - greater than cursor count " + " but not a shortcut."); - } - return SHORTCUT_INVALID; - } - - @Override - public boolean isEmpty() { - return getShortcutCount() == 0 && super.isEmpty(); - } - - @Override - public boolean isEnabled(int position) { - final int shortcutType = getShortcutTypeFromPosition(position); - if (shortcutType >= 0) { - return true; - } else { - return super.isEnabled(position); - } - } - - private void assignShortcutToView(ContactListItemView v, int shortcutType) { - final CharSequence text; - final Drawable drawable; - final Resources resources = getContext().getResources(); - final String number = getFormattedQueryString(); - switch (shortcutType) { - case SHORTCUT_DIRECT_CALL: - text = - ContactDisplayUtils.getTtsSpannedPhoneNumber( - resources, - R.string.search_shortcut_call_number, - bidiFormatter.unicodeWrap(number, TextDirectionHeuristics.LTR)); - drawable = ContextCompat.getDrawable(getContext(), R.drawable.quantum_ic_call_vd_theme_24); - break; - case SHORTCUT_CREATE_NEW_CONTACT: - text = resources.getString(R.string.search_shortcut_create_new_contact); - drawable = - ContextCompat.getDrawable(getContext(), R.drawable.quantum_ic_person_add_vd_theme_24); - drawable.setAutoMirrored(true); - break; - case SHORTCUT_ADD_TO_EXISTING_CONTACT: - text = resources.getString(R.string.search_shortcut_add_to_contact); - drawable = - ContextCompat.getDrawable(getContext(), R.drawable.quantum_ic_person_add_vd_theme_24); - break; - case SHORTCUT_SEND_SMS_MESSAGE: - text = resources.getString(R.string.search_shortcut_send_sms_message); - drawable = - ContextCompat.getDrawable(getContext(), R.drawable.quantum_ic_message_vd_theme_24); - break; - case SHORTCUT_MAKE_VIDEO_CALL: - text = resources.getString(R.string.search_shortcut_make_video_call); - drawable = - ContextCompat.getDrawable(getContext(), R.drawable.quantum_ic_videocam_vd_theme_24); - break; - case SHORTCUT_BLOCK_NUMBER: - text = resources.getString(R.string.search_shortcut_block_number); - drawable = - ContextCompat.getDrawable(getContext(), R.drawable.ic_not_interested_googblue_24dp); - break; - default: - throw new IllegalArgumentException("Invalid shortcut type"); - } - v.setDrawable(drawable); - v.setDisplayName(text); - v.setAdjustSelectionBoundsEnabled(false); - } - - /** @return True if the shortcut state (disabled vs enabled) was changed by this operation */ - public boolean setShortcutEnabled(int shortcutType, boolean visible) { - final boolean changed = shortcutEnabled[shortcutType] != visible; - shortcutEnabled[shortcutType] = visible; - return changed; - } - - public String getFormattedQueryString() { - return formattedQueryString; - } - - @Override - public void setQueryString(String queryString) { - formattedQueryString = - PhoneNumberHelper.formatNumber( - getContext(), PhoneNumberUtils.normalizeNumber(queryString), countryIso); - super.setQueryString(queryString); - } -} diff --git a/java/com/android/dialer/app/list/DialtactsPagerAdapter.java b/java/com/android/dialer/app/list/DialtactsPagerAdapter.java index d27293244..364ae6fad 100644 --- a/java/com/android/dialer/app/list/DialtactsPagerAdapter.java +++ b/java/com/android/dialer/app/list/DialtactsPagerAdapter.java @@ -56,11 +56,9 @@ public class DialtactsPagerAdapter extends FragmentPagerAdapter { private final List<Fragment> fragments = new ArrayList<>(); private final String[] tabTitles; private final boolean useNewSpeedDialTab; - private final boolean useNewContactsTab; private OldSpeedDialFragment oldSpeedDialFragment; private SpeedDialFragment speedDialFragment; private CallLogFragment callLogFragment; - private AllContactsFragment oldContactsFragment; private ContactsFragment contactsFragment; private CallLogFragment voicemailFragment; @@ -71,8 +69,6 @@ public class DialtactsPagerAdapter extends FragmentPagerAdapter { super(fm); useNewSpeedDialTab = ConfigProviderBindings.get(context).getBoolean("enable_new_favorites_tab", false); - useNewContactsTab = - ConfigProviderBindings.get(context).getBoolean("enable_new_contacts_tab", true); this.tabTitles = tabTitles; hasActiveVoicemailProvider = hasVoicemailProvider; fragments.addAll(Collections.nCopies(TAB_COUNT_WITH_VOICEMAIL, null)); @@ -105,17 +101,10 @@ public class DialtactsPagerAdapter extends FragmentPagerAdapter { } return callLogFragment; case TAB_INDEX_ALL_CONTACTS: - if (useNewContactsTab) { - if (contactsFragment == null) { - contactsFragment = ContactsFragment.newInstance(Header.ADD_CONTACT); - } - return contactsFragment; - } else { - if (oldContactsFragment == null) { - oldContactsFragment = new AllContactsFragment(); - } - return oldContactsFragment; + if (contactsFragment == null) { + contactsFragment = ContactsFragment.newInstance(Header.ADD_CONTACT); } + return contactsFragment; case TAB_INDEX_VOICEMAIL: if (voicemailFragment == null) { voicemailFragment = new VisualVoicemailCallLogFragment(); @@ -145,8 +134,6 @@ public class DialtactsPagerAdapter extends FragmentPagerAdapter { callLogFragment = (CallLogFragment) fragment; } else if (fragment instanceof ContactsFragment) { contactsFragment = (ContactsFragment) fragment; - } else if (fragment instanceof AllContactsFragment) { - oldContactsFragment = (AllContactsFragment) fragment; } else if (fragment instanceof CallLogFragment && position == TAB_INDEX_VOICEMAIL) { voicemailFragment = (CallLogFragment) fragment; LogUtil.v("ViewPagerAdapter.instantiateItem", voicemailFragment.toString()); diff --git a/java/com/android/dialer/app/list/RegularSearchFragment.java b/java/com/android/dialer/app/list/RegularSearchFragment.java deleted file mode 100644 index d1927f08a..000000000 --- a/java/com/android/dialer/app/list/RegularSearchFragment.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2013 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.dialer.app.list; - -import static android.Manifest.permission.READ_CONTACTS; - -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v13.app.FragmentCompat; -import android.view.LayoutInflater; -import android.view.ViewGroup; -import com.android.contacts.common.list.ContactEntryListAdapter; -import com.android.contacts.common.list.PinnedHeaderListView; -import com.android.dialer.app.R; -import com.android.dialer.callintent.CallInitiationType; -import com.android.dialer.common.LogUtil; -import com.android.dialer.common.concurrent.DialerExecutor; -import com.android.dialer.common.concurrent.DialerExecutor.Worker; -import com.android.dialer.common.concurrent.DialerExecutorComponent; -import com.android.dialer.phonenumbercache.CachedNumberLookupService; -import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo; -import com.android.dialer.phonenumbercache.PhoneNumberCache; -import com.android.dialer.util.PermissionsUtil; -import com.android.dialer.widget.EmptyContentView; -import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener; -import java.util.Arrays; - -public class RegularSearchFragment extends SearchFragment - implements OnEmptyViewActionButtonClickedListener, - FragmentCompat.OnRequestPermissionsResultCallback { - - public static final int PERMISSION_REQUEST_CODE = 1; - - private static final int SEARCH_DIRECTORY_RESULT_LIMIT = 5; - protected String permissionToRequest; - - private DialerExecutor<CachedContactInfo> addContactTask; - - public RegularSearchFragment() { - configureDirectorySearch(); - } - - public void configureDirectorySearch() { - setDirectorySearchEnabled(true); - setDirectoryResultLimit(SEARCH_DIRECTORY_RESULT_LIMIT); - } - - @Override - public void onCreate(Bundle savedState) { - super.onCreate(savedState); - - addContactTask = - DialerExecutorComponent.get(getContext()) - .dialerExecutorFactory() - .createUiTaskBuilder( - getFragmentManager(), - "RegularSearchFragment.addContact", - new AddContactWorker(getContext().getApplicationContext())) - .build(); - } - - @Override - protected void onCreateView(LayoutInflater inflater, ViewGroup container) { - super.onCreateView(inflater, container); - ((PinnedHeaderListView) getListView()).setScrollToSectionOnHeaderTouch(true); - } - - @Override - protected ContactEntryListAdapter createListAdapter() { - RegularSearchListAdapter adapter = new RegularSearchListAdapter(getActivity()); - adapter.setDisplayPhotos(true); - adapter.setUseCallableUri(usesCallableUri()); - adapter.setListener(this); - return adapter; - } - - @Override - protected void cacheContactInfo(int position) { - CachedNumberLookupService cachedNumberLookupService = - PhoneNumberCache.get(getContext()).getCachedNumberLookupService(); - if (cachedNumberLookupService != null) { - final RegularSearchListAdapter adapter = (RegularSearchListAdapter) getAdapter(); - CachedContactInfo cachedContactInfo = - adapter.getContactInfo(cachedNumberLookupService, position); - addContactTask.executeSerial(cachedContactInfo); - } - } - - @Override - protected void setupEmptyView() { - if (emptyView != null && getActivity() != null) { - final int imageResource; - final int actionLabelResource; - final int descriptionResource; - final OnEmptyViewActionButtonClickedListener listener; - if (!PermissionsUtil.hasPermission(getActivity(), READ_CONTACTS)) { - imageResource = R.drawable.empty_contacts; - actionLabelResource = R.string.permission_single_turn_on; - descriptionResource = R.string.permission_no_search; - listener = this; - permissionToRequest = READ_CONTACTS; - } else { - imageResource = EmptyContentView.NO_IMAGE; - actionLabelResource = EmptyContentView.NO_LABEL; - descriptionResource = EmptyContentView.NO_LABEL; - listener = null; - permissionToRequest = null; - } - - emptyView.setImage(imageResource); - emptyView.setActionLabel(actionLabelResource); - emptyView.setDescription(descriptionResource); - if (listener != null) { - emptyView.setActionClickedListener(listener); - } - } - } - - @Override - public void onEmptyViewActionButtonClicked() { - final Activity activity = getActivity(); - if (activity == null) { - return; - } - - if (READ_CONTACTS.equals(permissionToRequest)) { - String[] deniedPermissions = - PermissionsUtil.getPermissionsCurrentlyDenied( - getContext(), PermissionsUtil.allContactsGroupPermissionsUsedInDialer); - if (deniedPermissions.length > 0) { - LogUtil.i( - "RegularSearchFragment.onEmptyViewActionButtonClicked", - "Requesting permissions: " + Arrays.toString(deniedPermissions)); - FragmentCompat.requestPermissions(this, deniedPermissions, PERMISSION_REQUEST_CODE); - } - } - } - - @Override - public void onRequestPermissionsResult( - int requestCode, String[] permissions, int[] grantResults) { - if (requestCode == PERMISSION_REQUEST_CODE) { - setupEmptyView(); - if (grantResults != null - && grantResults.length == 1 - && PackageManager.PERMISSION_GRANTED == grantResults[0]) { - PermissionsUtil.notifyPermissionGranted(getActivity(), permissions[0]); - } - } - } - - @Override - protected CallInitiationType.Type getCallInitiationType(boolean isRemoteDirectory) { - return isRemoteDirectory - ? CallInitiationType.Type.REMOTE_DIRECTORY - : CallInitiationType.Type.REGULAR_SEARCH; - } - - public interface CapabilityChecker { - - boolean isNearbyPlacesSearchEnabled(); - } - - private static class AddContactWorker implements Worker<CachedContactInfo, Void> { - - private final Context appContext; - - private AddContactWorker(Context appContext) { - this.appContext = appContext; - } - - @Nullable - @Override - public Void doInBackground(@Nullable CachedContactInfo contactInfo) throws Throwable { - CachedNumberLookupService cachedNumberLookupService = - PhoneNumberCache.get(appContext).getCachedNumberLookupService(); - if (cachedNumberLookupService != null) { - cachedNumberLookupService.addContact(appContext, contactInfo); - } - return null; - } - } -} diff --git a/java/com/android/dialer/app/list/RegularSearchListAdapter.java b/java/com/android/dialer/app/list/RegularSearchListAdapter.java deleted file mode 100644 index c92f48c8b..000000000 --- a/java/com/android/dialer/app/list/RegularSearchListAdapter.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2013 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.dialer.app.list; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.text.TextUtils; -import com.android.contacts.common.ContactsUtils; -import com.android.contacts.common.list.DirectoryPartition; -import com.android.dialer.common.cp2.DirectoryCompat; -import com.android.dialer.phonenumbercache.CachedNumberLookupService; -import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo; -import com.android.dialer.phonenumbercache.ContactInfo; -import com.android.dialer.phonenumberutil.PhoneNumberHelper; -import com.android.dialer.util.CallUtil; - -/** List adapter to display regular search results. */ -public class RegularSearchListAdapter extends DialerPhoneNumberListAdapter { - - protected boolean isQuerySipAddress; - - public RegularSearchListAdapter(Context context) { - super(context); - setShortcutEnabled(SHORTCUT_CREATE_NEW_CONTACT, false); - setShortcutEnabled(SHORTCUT_ADD_TO_EXISTING_CONTACT, false); - } - - public CachedContactInfo getContactInfo(CachedNumberLookupService lookupService, int position) { - ContactInfo info = new ContactInfo(); - CachedContactInfo cacheInfo = lookupService.buildCachedContactInfo(info); - final Cursor item = (Cursor) getItem(position); - if (item != null) { - final DirectoryPartition partition = - (DirectoryPartition) getPartition(getPartitionForPosition(position)); - final long directoryId = partition.getDirectoryId(); - final boolean isExtendedDirectory = isExtendedDirectory(directoryId); - - info.name = item.getString(PhoneQuery.DISPLAY_NAME); - info.type = item.getInt(PhoneQuery.PHONE_TYPE); - info.label = item.getString(PhoneQuery.PHONE_LABEL); - info.number = item.getString(PhoneQuery.PHONE_NUMBER); - final String photoUriStr = item.getString(PhoneQuery.PHOTO_URI); - info.photoUri = photoUriStr == null ? null : Uri.parse(photoUriStr); - /* - * An extended directory is custom directory in the app, but not a directory provided by - * framework. So it can't be USER_TYPE_WORK. - * - * When a search result is selected, RegularSearchFragment calls getContactInfo and - * cache the resulting @{link ContactInfo} into local db. Set usertype to USER_TYPE_WORK - * only if it's NOT extended directory id and is enterprise directory. - */ - info.userType = - !isExtendedDirectory && DirectoryCompat.isEnterpriseDirectoryId(directoryId) - ? ContactsUtils.USER_TYPE_WORK - : ContactsUtils.USER_TYPE_CURRENT; - - cacheInfo.setLookupKey(item.getString(PhoneQuery.LOOKUP_KEY)); - - final String sourceName = partition.getLabel(); - if (isExtendedDirectory) { - cacheInfo.setExtendedSource(sourceName, directoryId); - } else { - cacheInfo.setDirectorySource(sourceName, directoryId); - } - } - return cacheInfo; - } - - @Override - public String getFormattedQueryString() { - if (isQuerySipAddress) { - // Return unnormalized SIP address - return getQueryString(); - } - return super.getFormattedQueryString(); - } - - @Override - public void setQueryString(String queryString) { - // Don't show actions if the query string contains a letter. - final boolean showNumberShortcuts = - !TextUtils.isEmpty(getFormattedQueryString()) && hasDigitsInQueryString(); - isQuerySipAddress = PhoneNumberHelper.isUriNumber(queryString); - - if (isChanged(showNumberShortcuts)) { - notifyDataSetChanged(); - } - super.setQueryString(queryString); - } - - protected boolean isChanged(boolean showNumberShortcuts) { - boolean changed = false; - changed |= setShortcutEnabled(SHORTCUT_DIRECT_CALL, showNumberShortcuts || isQuerySipAddress); - changed |= setShortcutEnabled(SHORTCUT_SEND_SMS_MESSAGE, showNumberShortcuts); - changed |= - setShortcutEnabled( - SHORTCUT_MAKE_VIDEO_CALL, showNumberShortcuts && CallUtil.isVideoEnabled(getContext())); - return changed; - } - - /** Whether there is at least one digit in the query string. */ - private boolean hasDigitsInQueryString() { - String queryString = getQueryString(); - int length = queryString.length(); - for (int i = 0; i < length; i++) { - if (Character.isDigit(queryString.charAt(i))) { - return true; - } - } - return false; - } -} diff --git a/java/com/android/dialer/app/list/SearchFragment.java b/java/com/android/dialer/app/list/SearchFragment.java deleted file mode 100644 index afb678969..000000000 --- a/java/com/android/dialer/app/list/SearchFragment.java +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Copyright (C) 2013 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.dialer.app.list; - -import android.animation.Animator; -import android.animation.AnimatorInflater; -import android.animation.AnimatorListenerAdapter; -import android.app.Activity; -import android.app.DialogFragment; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Interpolator; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.Space; -import com.android.contacts.common.list.ContactEntryListAdapter; -import com.android.contacts.common.list.OnPhoneNumberPickerActionListener; -import com.android.contacts.common.list.PhoneNumberPickerFragment; -import com.android.dialer.animation.AnimUtils; -import com.android.dialer.app.R; -import com.android.dialer.app.widget.DialpadSearchEmptyContentView; -import com.android.dialer.callintent.CallSpecificAppData; -import com.android.dialer.common.LogUtil; -import com.android.dialer.dialpadview.DialpadFragment.ErrorDialogFragment; -import com.android.dialer.logging.DialerImpression; -import com.android.dialer.logging.Logger; -import com.android.dialer.util.DialerUtils; -import com.android.dialer.util.IntentUtil; -import com.android.dialer.util.PermissionsUtil; -import com.android.dialer.widget.EmptyContentView; - -public class SearchFragment extends PhoneNumberPickerFragment { - - protected EmptyContentView emptyView; - private OnListFragmentScrolledListener activityScrollListener; - private View.OnTouchListener activityOnTouchListener; - /* - * Stores the untouched user-entered string that is used to populate the add to contacts - * intent. - */ - private String addToContactNumber; - private int actionBarHeight; - private int shadowHeight; - private int paddingTop; - private int showDialpadDuration; - private int hideDialpadDuration; - /** - * Used to resize the list view containing search results so that it fits the available space - * above the dialpad. Does not have a user-visible effect in regular touch usage (since the - * dialpad hides that portion of the ListView anyway), but improves usability in accessibility - * mode. - */ - private Space spacer; - - private HostInterface activity; - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - - setQuickContactEnabled(true); - setAdjustSelectionBoundsEnabled(false); - setDarkTheme(false); - setUseCallableUri(true); - - try { - activityScrollListener = (OnListFragmentScrolledListener) activity; - } catch (ClassCastException e) { - LogUtil.v( - "SearchFragment.onAttach", - activity.toString() - + " doesn't implement OnListFragmentScrolledListener. " - + "Ignoring."); - } - } - - @Override - public void onStart() { - LogUtil.d("SearchFragment.onStart", ""); - super.onStart(); - - activity = (HostInterface) getActivity(); - - final Resources res = getResources(); - actionBarHeight = activity.getActionBarHeight(); - shadowHeight = res.getDrawable(R.drawable.search_shadow).getIntrinsicHeight(); - paddingTop = res.getDimensionPixelSize(R.dimen.search_list_padding_top); - showDialpadDuration = res.getInteger(R.integer.dialpad_slide_in_duration); - hideDialpadDuration = res.getInteger(R.integer.dialpad_slide_out_duration); - - final ListView listView = getListView(); - - if (emptyView == null) { - if (this instanceof SmartDialSearchFragment) { - emptyView = new DialpadSearchEmptyContentView(getActivity()); - } else { - emptyView = new EmptyContentView(getActivity()); - } - ((ViewGroup) getListView().getParent()).addView(emptyView); - getListView().setEmptyView(emptyView); - setupEmptyView(); - } - - listView.setBackgroundColor(res.getColor(R.color.background_dialer_results)); - listView.setClipToPadding(false); - setVisibleScrollbarEnabled(false); - - //Turn of accessibility live region as the list constantly update itself and spam messages. - listView.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_NONE); - ContentChangedFilter.addToParent(listView); - - listView.setOnScrollListener( - new OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - if (activityScrollListener != null) { - activityScrollListener.onListFragmentScrollStateChange(scrollState); - } - } - - @Override - public void onScroll( - AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {} - }); - if (activityOnTouchListener != null) { - listView.setOnTouchListener(activityOnTouchListener); - } - - updatePosition(false /* animate */); - } - - @Override - public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) { - Animator animator = null; - if (nextAnim != 0) { - animator = AnimatorInflater.loadAnimator(getActivity(), nextAnim); - } - if (animator != null) { - final View view = getView(); - final int oldLayerType = view.getLayerType(); - animator.addListener( - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setLayerType(oldLayerType, null); - } - }); - } - return animator; - } - - public void setAddToContactNumber(String addToContactNumber) { - this.addToContactNumber = addToContactNumber; - } - - /** - * Return true if phone number is prohibited by a value - - * (R.string.config_prohibited_phone_number_regexp) in the config files. False otherwise. - */ - public boolean checkForProhibitedPhoneNumber(String number) { - // Regular expression prohibiting manual phone call. Can be empty i.e. "no rule". - String prohibitedPhoneNumberRegexp = - getResources().getString(R.string.config_prohibited_phone_number_regexp); - - // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated - // test equipment. - if (number != null - && !TextUtils.isEmpty(prohibitedPhoneNumberRegexp) - && number.matches(prohibitedPhoneNumberRegexp)) { - LogUtil.i( - "SearchFragment.checkForProhibitedPhoneNumber", - "the phone number is prohibited explicitly by a rule"); - if (getActivity() != null) { - DialogFragment dialogFragment = - ErrorDialogFragment.newInstance(R.string.dialog_phone_call_prohibited_message); - dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog"); - } - - return true; - } - return false; - } - - @Override - protected ContactEntryListAdapter createListAdapter() { - DialerPhoneNumberListAdapter adapter = new DialerPhoneNumberListAdapter(getActivity()); - adapter.setDisplayPhotos(true); - adapter.setUseCallableUri(super.usesCallableUri()); - adapter.setListener(this); - return adapter; - } - - @Override - protected void onItemClick(int position, long id) { - final DialerPhoneNumberListAdapter adapter = (DialerPhoneNumberListAdapter) getAdapter(); - final int shortcutType = adapter.getShortcutTypeFromPosition(position); - final OnPhoneNumberPickerActionListener listener; - final Intent intent; - final String number; - - LogUtil.i("SearchFragment.onItemClick", "shortcutType: " + shortcutType); - - switch (shortcutType) { - case DialerPhoneNumberListAdapter.SHORTCUT_DIRECT_CALL: - number = adapter.getQueryString(); - listener = getOnPhoneNumberPickerListener(); - if (listener != null && !checkForProhibitedPhoneNumber(number)) { - CallSpecificAppData callSpecificAppData = - CallSpecificAppData.newBuilder() - .setCallInitiationType(getCallInitiationType(false /* isRemoteDirectory */)) - .setPositionOfSelectedSearchResult(position) - .setCharactersInSearchString( - getQueryString() == null ? 0 : getQueryString().length()) - .build(); - listener.onPickPhoneNumber(number, false /* isVideoCall */, callSpecificAppData); - } - break; - case DialerPhoneNumberListAdapter.SHORTCUT_CREATE_NEW_CONTACT: - if (this instanceof SmartDialSearchFragment) { - Logger.get(getContext()) - .logImpression(DialerImpression.Type.CREATE_NEW_CONTACT_FROM_DIALPAD); - } - number = - TextUtils.isEmpty(addToContactNumber) - ? adapter.getFormattedQueryString() - : addToContactNumber; - intent = IntentUtil.getNewContactIntent(number); - DialerUtils.startActivityWithErrorToast(getActivity(), intent); - break; - case DialerPhoneNumberListAdapter.SHORTCUT_ADD_TO_EXISTING_CONTACT: - if (this instanceof SmartDialSearchFragment) { - Logger.get(getContext()) - .logImpression(DialerImpression.Type.ADD_TO_A_CONTACT_FROM_DIALPAD); - } - number = - TextUtils.isEmpty(addToContactNumber) - ? adapter.getFormattedQueryString() - : addToContactNumber; - intent = IntentUtil.getAddToExistingContactIntent(number); - DialerUtils.startActivityWithErrorToast( - getActivity(), intent, R.string.add_contact_not_available); - break; - case DialerPhoneNumberListAdapter.SHORTCUT_SEND_SMS_MESSAGE: - number = - TextUtils.isEmpty(addToContactNumber) - ? adapter.getFormattedQueryString() - : addToContactNumber; - intent = IntentUtil.getSendSmsIntent(number); - DialerUtils.startActivityWithErrorToast(getActivity(), intent); - break; - case DialerPhoneNumberListAdapter.SHORTCUT_MAKE_VIDEO_CALL: - number = - TextUtils.isEmpty(addToContactNumber) ? adapter.getQueryString() : addToContactNumber; - listener = getOnPhoneNumberPickerListener(); - if (listener != null && !checkForProhibitedPhoneNumber(number)) { - CallSpecificAppData callSpecificAppData = - CallSpecificAppData.newBuilder() - .setCallInitiationType(getCallInitiationType(false /* isRemoteDirectory */)) - .setPositionOfSelectedSearchResult(position) - .setCharactersInSearchString( - getQueryString() == null ? 0 : getQueryString().length()) - .build(); - listener.onPickPhoneNumber(number, true /* isVideoCall */, callSpecificAppData); - } - break; - case DialerPhoneNumberListAdapter.SHORTCUT_INVALID: - default: - super.onItemClick(position, id); - break; - } - } - - /** - * Updates the position and padding of the search fragment, depending on whether the dialpad is - * shown. This can be optionally animated. - */ - public void updatePosition(boolean animate) { - LogUtil.d("SearchFragment.updatePosition", "animate: %b", animate); - if (activity == null) { - // Activity will be set in onStart, and this method will be called again - return; - } - - // Use negative shadow height instead of 0 to account for the 9-patch's shadow. - int startTranslationValue = - activity.isDialpadShown() ? actionBarHeight - shadowHeight : -shadowHeight; - int endTranslationValue = 0; - // Prevents ListView from being translated down after a rotation when the ActionBar is up. - if (animate || activity.isActionBarShowing()) { - endTranslationValue = activity.isDialpadShown() ? 0 : actionBarHeight - shadowHeight; - } - if (animate) { - // If the dialpad will be shown, then this animation involves sliding the list up. - final boolean slideUp = activity.isDialpadShown(); - - Interpolator interpolator = slideUp ? AnimUtils.EASE_IN : AnimUtils.EASE_OUT; - int duration = slideUp ? showDialpadDuration : hideDialpadDuration; - getView().setTranslationY(startTranslationValue); - getView() - .animate() - .translationY(endTranslationValue) - .setInterpolator(interpolator) - .setDuration(duration) - .setListener( - new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - if (!slideUp) { - resizeListView(); - } - } - - @Override - public void onAnimationEnd(Animator animation) { - if (slideUp) { - resizeListView(); - } - } - }); - - } else { - getView().setTranslationY(endTranslationValue); - resizeListView(); - } - - // There is padding which should only be applied when the dialpad is not shown. - int paddingTop = activity.isDialpadShown() ? 0 : this.paddingTop; - final ListView listView = getListView(); - listView.setPaddingRelative( - listView.getPaddingStart(), - paddingTop, - listView.getPaddingEnd(), - listView.getPaddingBottom()); - } - - public void resizeListView() { - if (spacer == null) { - return; - } - int spacerHeight = activity.isDialpadShown() ? activity.getDialpadHeight() : 0; - LogUtil.d( - "SearchFragment.resizeListView", - "spacerHeight: %d -> %d, isDialpadShown: %b, dialpad height: %d", - spacer.getHeight(), - spacerHeight, - activity.isDialpadShown(), - activity.getDialpadHeight()); - if (spacerHeight != spacer.getHeight()) { - final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) spacer.getLayoutParams(); - lp.height = spacerHeight; - spacer.setLayoutParams(lp); - } - } - - @Override - protected void startLoading() { - if (getActivity() == null) { - return; - } - - if (PermissionsUtil.hasContactsReadPermissions(getActivity())) { - super.startLoading(); - } else if (TextUtils.isEmpty(getQueryString())) { - // Clear out any existing call shortcuts. - final DialerPhoneNumberListAdapter adapter = (DialerPhoneNumberListAdapter) getAdapter(); - adapter.disableAllShortcuts(); - } else { - // The contact list is not going to change (we have no results since permissions are - // denied), but the shortcuts might because of the different query, so update the - // list. - getAdapter().notifyDataSetChanged(); - } - - setupEmptyView(); - } - - public void setOnTouchListener(View.OnTouchListener onTouchListener) { - activityOnTouchListener = onTouchListener; - } - - @Override - protected View inflateView(LayoutInflater inflater, ViewGroup container) { - final LinearLayout parent = (LinearLayout) super.inflateView(inflater, container); - final int orientation = getResources().getConfiguration().orientation; - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - spacer = new Space(getActivity()); - parent.addView( - spacer, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0)); - } - return parent; - } - - protected void setupEmptyView() {} - - public interface HostInterface { - - boolean isActionBarShowing(); - - boolean isDialpadShown(); - - int getDialpadHeight(); - - int getActionBarHeight(); - } -} diff --git a/java/com/android/dialer/app/list/SmartDialNumberListAdapter.java b/java/com/android/dialer/app/list/SmartDialNumberListAdapter.java deleted file mode 100644 index c84bff7fc..000000000 --- a/java/com/android/dialer/app/list/SmartDialNumberListAdapter.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2013 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.dialer.app.list; - -import android.content.Context; -import android.database.Cursor; -import android.support.annotation.NonNull; -import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; -import com.android.contacts.common.list.ContactListItemView; -import com.android.dialer.common.LogUtil; -import com.android.dialer.smartdial.SmartDialCursorLoader; -import com.android.dialer.smartdial.util.SmartDialMatchPosition; -import com.android.dialer.smartdial.util.SmartDialNameMatcher; -import com.android.dialer.util.CallUtil; -import java.util.ArrayList; - -/** List adapter to display the SmartDial search results. */ -public class SmartDialNumberListAdapter extends DialerPhoneNumberListAdapter { - - private static final String TAG = SmartDialNumberListAdapter.class.getSimpleName(); - private static final boolean DEBUG = false; - - private final Context context; - @NonNull private final SmartDialNameMatcher nameMatcher; - - public SmartDialNumberListAdapter(Context context) { - super(context); - this.context = context; - nameMatcher = new SmartDialNameMatcher(""); - setShortcutEnabled(SmartDialNumberListAdapter.SHORTCUT_DIRECT_CALL, false); - - if (DEBUG) { - LogUtil.v(TAG, "Constructing List Adapter"); - } - } - - /** Sets query for the SmartDialCursorLoader. */ - public void configureLoader(SmartDialCursorLoader loader) { - if (DEBUG) { - LogUtil.v(TAG, "Configure Loader with query" + getQueryString()); - } - - if (getQueryString() == null) { - loader.configureQuery(""); - nameMatcher.setQuery(""); - } else { - loader.configureQuery(getQueryString()); - nameMatcher.setQuery(PhoneNumberUtils.normalizeNumber(getQueryString())); - } - } - - /** - * Sets highlight options for a List item in the SmartDial search results. - * - * @param view ContactListItemView where the result will be displayed. - * @param cursor Object containing information of the associated List item. - */ - @Override - protected void setHighlight(ContactListItemView view, Cursor cursor) { - view.clearHighlightSequences(); - - if (nameMatcher.matches(context, cursor.getString(PhoneQuery.DISPLAY_NAME))) { - final ArrayList<SmartDialMatchPosition> nameMatches = nameMatcher.getMatchPositions(); - for (SmartDialMatchPosition match : nameMatches) { - view.addNameHighlightSequence(match.start, match.end); - if (DEBUG) { - LogUtil.v( - TAG, - cursor.getString(PhoneQuery.DISPLAY_NAME) - + " " - + nameMatcher.getQuery() - + " " - + String.valueOf(match.start)); - } - } - } - - final SmartDialMatchPosition numberMatch = - nameMatcher.matchesNumber(context, cursor.getString(PhoneQuery.PHONE_NUMBER)); - if (numberMatch != null) { - view.addNumberHighlightSequence(numberMatch.start, numberMatch.end); - } - } - - @Override - public void setQueryString(String queryString) { - final boolean showNumberShortcuts = !TextUtils.isEmpty(getFormattedQueryString()); - boolean changed = false; - changed |= setShortcutEnabled(SHORTCUT_CREATE_NEW_CONTACT, showNumberShortcuts); - changed |= setShortcutEnabled(SHORTCUT_ADD_TO_EXISTING_CONTACT, showNumberShortcuts); - changed |= setShortcutEnabled(SHORTCUT_SEND_SMS_MESSAGE, showNumberShortcuts); - changed |= - setShortcutEnabled( - SHORTCUT_MAKE_VIDEO_CALL, showNumberShortcuts && CallUtil.isVideoEnabled(getContext())); - if (changed) { - notifyDataSetChanged(); - } - super.setQueryString(queryString); - } - - public void setShowEmptyListForNullQuery(boolean show) { - nameMatcher.setShouldMatchEmptyQuery(!show); - } -} diff --git a/java/com/android/dialer/app/list/SmartDialSearchFragment.java b/java/com/android/dialer/app/list/SmartDialSearchFragment.java deleted file mode 100644 index fdf0b5a56..000000000 --- a/java/com/android/dialer/app/list/SmartDialSearchFragment.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2013 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.dialer.app.list; - -import static android.Manifest.permission.CALL_PHONE; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.Loader; -import android.database.Cursor; -import android.os.Bundle; -import android.support.v13.app.FragmentCompat; -import com.android.contacts.common.list.ContactEntryListAdapter; -import com.android.dialer.app.R; -import com.android.dialer.callintent.CallInitiationType; -import com.android.dialer.common.LogUtil; -import com.android.dialer.database.DialerDatabaseHelper; -import com.android.dialer.smartdial.SmartDialCursorLoader; -import com.android.dialer.util.PermissionsUtil; -import com.android.dialer.widget.EmptyContentView; -import java.util.Arrays; - -/** Implements a fragment to load and display SmartDial search results. */ -public class SmartDialSearchFragment extends SearchFragment - implements EmptyContentView.OnEmptyViewActionButtonClickedListener, - FragmentCompat.OnRequestPermissionsResultCallback { - - private static final int CALL_PHONE_PERMISSION_REQUEST_CODE = 1; - - private final BroadcastReceiver smartDialUpdatedReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - LogUtil.i("SmartDialSearchFragment.onReceive", "smart dial update broadcast received"); - reloadData(); - } - }; - - /** Creates a SmartDialListAdapter to display and operate on search results. */ - @Override - protected ContactEntryListAdapter createListAdapter() { - SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity()); - adapter.setUseCallableUri(super.usesCallableUri()); - adapter.setQuickContactEnabled(true); - adapter.setShowEmptyListForNullQuery(getShowEmptyListForNullQuery()); - // Set adapter's query string to restore previous instance state. - adapter.setQueryString(getQueryString()); - adapter.setListener(this); - return adapter; - } - - /** Creates a SmartDialCursorLoader object to load query results. */ - @Override - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - // Smart dialing does not support Directory Load, falls back to normal search instead. - if (id == getDirectoryLoaderId()) { - return super.onCreateLoader(id, args); - } else { - final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter(); - SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext()); - loader.setShowEmptyListForNullQuery(getShowEmptyListForNullQuery()); - adapter.configureLoader(loader); - return loader; - } - } - - @Override - public boolean getShowEmptyListForNullQuery() { - return true; - } - - @Override - protected void setupEmptyView() { - if (emptyView != null && getActivity() != null) { - if (!PermissionsUtil.hasPermission(getActivity(), CALL_PHONE)) { - emptyView.setImage(R.drawable.empty_contacts); - emptyView.setActionLabel(R.string.permission_single_turn_on); - emptyView.setDescription(R.string.permission_place_call); - emptyView.setActionClickedListener(this); - } else { - emptyView.setImage(EmptyContentView.NO_IMAGE); - emptyView.setActionLabel(EmptyContentView.NO_LABEL); - emptyView.setDescription(EmptyContentView.NO_LABEL); - } - } - } - - @Override - public void onStart() { - super.onStart(); - - LogUtil.i("SmartDialSearchFragment.onStart", "registering smart dial update receiver"); - - getActivity() - .registerReceiver( - smartDialUpdatedReceiver, - new IntentFilter(DialerDatabaseHelper.ACTION_SMART_DIAL_UPDATED)); - } - - @Override - public void onStop() { - super.onStop(); - - LogUtil.i("SmartDialSearchFragment.onStop", "unregistering smart dial update receiver"); - - getActivity().unregisterReceiver(smartDialUpdatedReceiver); - } - - @Override - public void onEmptyViewActionButtonClicked() { - final Activity activity = getActivity(); - if (activity == null) { - return; - } - - String[] deniedPermissions = - PermissionsUtil.getPermissionsCurrentlyDenied( - getContext(), PermissionsUtil.allPhoneGroupPermissionsUsedInDialer); - if (deniedPermissions.length > 0) { - LogUtil.i( - "SmartDialSearchFragment.onEmptyViewActionButtonClicked", - "Requesting permissions: " + Arrays.toString(deniedPermissions)); - FragmentCompat.requestPermissions( - this, deniedPermissions, CALL_PHONE_PERMISSION_REQUEST_CODE); - } - } - - @Override - public void onRequestPermissionsResult( - int requestCode, String[] permissions, int[] grantResults) { - if (requestCode == CALL_PHONE_PERMISSION_REQUEST_CODE) { - setupEmptyView(); - } - } - - @Override - protected CallInitiationType.Type getCallInitiationType(boolean isRemoteDirectory) { - return CallInitiationType.Type.SMART_DIAL; - } - - public boolean isShowingPermissionRequest() { - return emptyView != null && emptyView.isShowingContent(); - } - - @Override - public void setShowEmptyListForNullQuery(boolean show) { - if (getAdapter() != null) { - ((SmartDialNumberListAdapter) getAdapter()).setShowEmptyListForNullQuery(show); - } - super.setShowEmptyListForNullQuery(show); - } -} diff --git a/java/com/android/dialer/app/res/layout/all_contacts_fragment.xml b/java/com/android/dialer/app/res/layout/all_contacts_fragment.xml deleted file mode 100644 index 422c52991..000000000 --- a/java/com/android/dialer/app/res/layout/all_contacts_fragment.xml +++ /dev/null @@ -1,56 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2013 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. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/pinned_header_list_layout" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <!-- Shown only when an Account filter is set. - - paddingTop should be here to show "shade" effect correctly. --> - <!-- TODO: Remove the filter header. --> - <include layout="@layout/account_filter_header"/> - - <FrameLayout - android:layout_width="match_parent" - android:layout_height="0dip" - android:layout_weight="1"> - <view - android:id="@android:id/list" - style="@style/DialtactsTheme" - class="com.android.contacts.common.list.PinnedHeaderListView" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_marginStart="?attr/contact_browser_list_padding_left" - android:layout_marginEnd="?attr/contact_browser_list_padding_right" - android:paddingTop="18dp" - android:fadingEdge="none" - android:fastScrollEnabled="true" - android:nestedScrollingEnabled="true" - android:cropToPadding="false" - android:clipToPadding="false"/> - - <com.android.dialer.widget.EmptyContentView - android:id="@+id/empty_list_view" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:visibility="gone"/> - - </FrameLayout> -</LinearLayout> diff --git a/java/com/android/dialer/app/res/layout/blocked_number_header.xml b/java/com/android/dialer/app/res/layout/blocked_number_header.xml index e34510b73..e1019d1eb 100644 --- a/java/com/android/dialer/app/res/layout/blocked_number_header.xml +++ b/java/com/android/dialer/app/res/layout/blocked_number_header.xml @@ -166,46 +166,6 @@ </LinearLayout> - <LinearLayout - android:id="@+id/add_number_linear_layout" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="@dimen/blocked_number_add_top_margin" - android:paddingBottom="@dimen/blocked_number_add_bottom_margin" - android:paddingStart="@dimen/blocked_number_horizontal_margin" - android:background="?android:attr/selectableItemBackground" - android:baselineAligned="false" - android:clickable="true" - android:contentDescription="@string/addBlockedNumber" - android:focusable="true" - android:gravity="center_vertical" - android:orientation="horizontal"> - - <ImageView - android:id="@+id/add_number_icon" - android:layout_width="@dimen/contact_photo_size" - android:layout_height="@dimen/contact_photo_size" - android:importantForAccessibility="no"/> - <LinearLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_marginStart="@dimen/blocked_number_horizontal_margin" - android:gravity="center_vertical" - android:orientation="vertical"> - - <TextView - android:id="@+id/add_number_textview" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:includeFontPadding="false" - android:text="@string/addBlockedNumber" - android:textColor="@color/blocked_number_primary_text_color" - android:textSize="@dimen/blocked_number_primary_text_size"/> - </LinearLayout> - - </LinearLayout> - <View android:id="@+id/blocked_number_list_divider" android:layout_width="match_parent" diff --git a/java/com/android/dialer/assisteddialing/AndroidManifest.xml b/java/com/android/dialer/assisteddialing/AndroidManifest.xml index a19af3b72..0a5b613d7 100644 --- a/java/com/android/dialer/assisteddialing/AndroidManifest.xml +++ b/java/com/android/dialer/assisteddialing/AndroidManifest.xml @@ -16,7 +16,7 @@ package="com.android.dialer.assisteddialing"> <uses-sdk - android:minSdkVersion="23" + android:minSdkVersion="24" android:targetSdkVersion="27"/> </manifest> diff --git a/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml b/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml index 3e79de38a..d59056eb0 100644 --- a/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml +++ b/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml @@ -16,7 +16,7 @@ package="com.android.dialer.assisteddialing.ui"> <uses-sdk - android:minSdkVersion="23" + android:minSdkVersion="24" android:targetSdkVersion="27"/> <application> diff --git a/java/com/android/dialer/binary/google/AndroidManifest.xml b/java/com/android/dialer/binary/google/AndroidManifest.xml index c1f1a59bf..f6ac430b3 100644 --- a/java/com/android/dialer/binary/google/AndroidManifest.xml +++ b/java/com/android/dialer/binary/google/AndroidManifest.xml @@ -20,7 +20,7 @@ android:versionName="19.0"> <uses-sdk - android:minSdkVersion="23" + android:minSdkVersion="24" android:targetSdkVersion="27"/> <uses-permission android:name="android.permission.CALL_PHONE"/> diff --git a/java/com/android/dialer/blocking/BlockedNumbersMigrator.java b/java/com/android/dialer/blocking/BlockedNumbersMigrator.java index 88f474a84..61ebf2f56 100644 --- a/java/com/android/dialer/blocking/BlockedNumbersMigrator.java +++ b/java/com/android/dialer/blocking/BlockedNumbersMigrator.java @@ -35,7 +35,7 @@ import java.util.Objects; * Class which should be used to migrate numbers from {@link FilteredNumberContract} blocking to * {@link android.provider.BlockedNumberContract} blocking. */ -@TargetApi(VERSION_CODES.M) +@TargetApi(VERSION_CODES.N) public class BlockedNumbersMigrator { private final Context context; diff --git a/java/com/android/dialer/blocking/FilteredNumberAsyncQueryHandler.java b/java/com/android/dialer/blocking/FilteredNumberAsyncQueryHandler.java index 09fd5f0a8..8be479c99 100644 --- a/java/com/android/dialer/blocking/FilteredNumberAsyncQueryHandler.java +++ b/java/com/android/dialer/blocking/FilteredNumberAsyncQueryHandler.java @@ -37,6 +37,7 @@ import com.android.dialer.database.FilteredNumberContract.FilteredNumberTypes; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +/** TODO(calderwoodra): documentation */ public class FilteredNumberAsyncQueryHandler extends AsyncQueryHandler { public static final int INVALID_ID = -1; @@ -199,7 +200,7 @@ public class FilteredNumberAsyncQueryHandler extends AsyncQueryHandler { * * @return blocked id. */ - @TargetApi(VERSION_CODES.M) + @TargetApi(VERSION_CODES.N) @Nullable public Integer getBlockedIdSynchronous(@Nullable String number, String countryIso) { Assert.isWorkerThread(); @@ -382,6 +383,7 @@ public class FilteredNumberAsyncQueryHandler extends AsyncQueryHandler { null); } + /** TODO(calderwoodra): documentation */ public interface OnCheckBlockedListener { /** @@ -392,6 +394,7 @@ public class FilteredNumberAsyncQueryHandler extends AsyncQueryHandler { void onCheckComplete(Integer id); } + /** TODO(calderwoodra): documentation */ public interface OnBlockNumberListener { /** @@ -402,6 +405,7 @@ public class FilteredNumberAsyncQueryHandler extends AsyncQueryHandler { void onBlockComplete(Uri uri); } + /** TODO(calderwoodra): documentation */ public interface OnUnblockNumberListener { /** @@ -413,6 +417,7 @@ public class FilteredNumberAsyncQueryHandler extends AsyncQueryHandler { void onUnblockComplete(int rows, ContentValues values); } + /** TODO(calderwoodra): documentation */ interface OnHasBlockedNumbersListener { /** diff --git a/java/com/android/dialer/blocking/FilteredNumberCompat.java b/java/com/android/dialer/blocking/FilteredNumberCompat.java index bea84e8db..b0af45c97 100644 --- a/java/com/android/dialer/blocking/FilteredNumberCompat.java +++ b/java/com/android/dialer/blocking/FilteredNumberCompat.java @@ -16,16 +16,12 @@ package com.android.dialer.blocking; -import android.annotation.TargetApi; import android.app.FragmentManager; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.os.UserManager; import android.preference.PreferenceManager; import android.provider.BlockedNumberContract; import android.provider.BlockedNumberContract.BlockedNumbers; @@ -110,7 +106,7 @@ public class FilteredNumberCompat { * otherwise. */ public static boolean canUseNewFiltering() { - return VERSION.SDK_INT >= VERSION_CODES.N; + return true; } /** @@ -162,16 +158,14 @@ public class FilteredNumberCompat { private static Uri getBaseUri(Context context) { // Explicit version check to aid static analysis - return useNewFiltering(context) && VERSION.SDK_INT >= VERSION_CODES.N - ? BlockedNumbers.CONTENT_URI - : FilteredNumber.CONTENT_URI; + return useNewFiltering(context) ? BlockedNumbers.CONTENT_URI : FilteredNumber.CONTENT_URI; } /** * Removes any null column names from the given projection array. This method is intended to be * used to strip out any column names that aren't available in every version of number blocking. * Example: {@literal getContext().getContentResolver().query( someUri, // Filtering ensures that - * no non-existant columns are queried FilteredNumberCompat.filter(new String[] + * no non-existent columns are queried FilteredNumberCompat.filter(new String[] * {FilteredNumberCompat.getIdColumnName(), FilteredNumberCompat.getTypeColumnName()}, * FilteredNumberCompat.getE164NumberColumnName() + " = ?", new String[] {e164Number}); } * @@ -249,9 +243,7 @@ public class FilteredNumberCompat { */ public static Intent createManageBlockedNumbersIntent(Context context) { // Explicit version check to aid static analysis - if (canUseNewFiltering() - && hasMigratedToNewBlocking(context) - && VERSION.SDK_INT >= VERSION_CODES.N) { + if (canUseNewFiltering() && hasMigratedToNewBlocking(context)) { return context.getSystemService(TelecomManager.class).createManageBlockedNumbersIntent(); } Intent intent = new Intent("com.android.dialer.action.BLOCKED_NUMBERS_SETTINGS"); @@ -270,11 +262,6 @@ public class FilteredNumberCompat { return canAttemptBlockOperationsForTest; } - if (VERSION.SDK_INT < VERSION_CODES.N) { - // Dialer blocking, must be primary user - return context.getSystemService(UserManager.class).isSystemUser(); - } - // Great Wall blocking, must be primary user and the default or system dialer // TODO(maxwelb): check that we're the system Dialer return TelecomUtil.isDefaultDialer(context) @@ -294,10 +281,6 @@ public class FilteredNumberCompat { * otherwise. */ public static boolean canCurrentUserOpenBlockSettings(Context context) { - if (VERSION.SDK_INT < VERSION_CODES.N) { - // Dialer blocking, must be primary user - return context.getSystemService(UserManager.class).isSystemUser(); - } // BlockedNumberContract blocking, verify through Contract API return TelecomUtil.isDefaultDialer(context) && safeBlockedNumbersContractCanCurrentUserBlockNumbers(context); @@ -312,7 +295,6 @@ public class FilteredNumberCompat { * @return the result of BlockedNumberContract#canCurrentUserBlockNumbers, or {@code false} if an * exception was thrown. */ - @TargetApi(VERSION_CODES.N) private static boolean safeBlockedNumbersContractCanCurrentUserBlockNumbers(Context context) { try { return BlockedNumberContract.canCurrentUserBlockNumbers(context); diff --git a/java/com/android/dialer/callcomposer/CopyAndResizeImageWorker.java b/java/com/android/dialer/callcomposer/CopyAndResizeImageWorker.java index 725cea723..0f1ab5f25 100644 --- a/java/com/android/dialer/callcomposer/CopyAndResizeImageWorker.java +++ b/java/com/android/dialer/callcomposer/CopyAndResizeImageWorker.java @@ -37,7 +37,7 @@ import java.io.InputStream; import java.io.OutputStream; /** Task for copying and resizing images to be shared with RCS process. */ -@TargetApi(VERSION_CODES.M) +@TargetApi(VERSION_CODES.N) class CopyAndResizeImageWorker implements Worker<Uri, Pair<File, String>> { private static final String MIME_TYPE = "image/jpeg"; diff --git a/java/com/android/dialer/callcomposer/camera/ImagePersistWorker.java b/java/com/android/dialer/callcomposer/camera/ImagePersistWorker.java index 69f546929..c18e22d56 100644 --- a/java/com/android/dialer/callcomposer/camera/ImagePersistWorker.java +++ b/java/com/android/dialer/callcomposer/camera/ImagePersistWorker.java @@ -38,7 +38,7 @@ import java.io.IOException; import java.io.OutputStream; /** Persisting image routine. */ -@TargetApi(VERSION_CODES.M) +@TargetApi(VERSION_CODES.N) public class ImagePersistWorker implements Worker<Void, Result> { private int width; private int height; diff --git a/java/com/android/dialer/calllog/AndroidManifest.xml b/java/com/android/dialer/calllog/AndroidManifest.xml index 69731fe39..7c904d993 100644 --- a/java/com/android/dialer/calllog/AndroidManifest.xml +++ b/java/com/android/dialer/calllog/AndroidManifest.xml @@ -17,7 +17,7 @@ package="com.android.dialer.calllog"> <uses-sdk - android:minSdkVersion="23" + android:minSdkVersion="24" android:targetSdkVersion="27"/> <application> diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java index 9a80af2f7..7fc474a98 100644 --- a/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java +++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java @@ -92,7 +92,7 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { return true; } - @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources + @TargetApi(Build.VERSION_CODES.N) // Uses try-with-resources @Nullable @Override public Cursor query( diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java index 6fd19dd17..dce51b750 100644 --- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java @@ -256,7 +256,7 @@ public class SystemCallLogDataSource implements CallLogDataSource { } } - @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources + @TargetApi(Build.VERSION_CODES.N) // Uses try-with-resources private void handleInsertsAndUpdates( Context appContext, CallLogMutations mutations, Set<Long> existingAnnotatedCallLogIds) { long previousTimestampProcessed = sharedPreferences.getLong(PREF_LAST_TIMESTAMP_PROCESSED, 0L); @@ -481,7 +481,7 @@ public class SystemCallLogDataSource implements CallLogDataSource { } } - @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources + @TargetApi(Build.VERSION_CODES.N) // Uses try-with-resources private static Set<Long> getAnnotatedCallLogIds(Context appContext) { ArraySet<Long> ids = new ArraySet<>(); @@ -510,7 +510,7 @@ public class SystemCallLogDataSource implements CallLogDataSource { return ids; } - @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources + @TargetApi(Build.VERSION_CODES.N) // Uses try-with-resources private static Set<Long> getIdsFromSystemCallLogThatMatch( Context appContext, Set<Long> matchingIds) { ArraySet<Long> ids = new ArraySet<>(); diff --git a/java/com/android/dialer/calllogutils/CallLogDates.java b/java/com/android/dialer/calllogutils/CallLogDates.java index fe3c0c3ad..5a63c3c8d 100644 --- a/java/com/android/dialer/calllogutils/CallLogDates.java +++ b/java/com/android/dialer/calllogutils/CallLogDates.java @@ -19,8 +19,6 @@ package com.android.dialer.calllogutils; import android.content.Context; import android.icu.lang.UCharacter; import android.icu.text.BreakIterator; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.text.format.DateUtils; import java.util.Calendar; import java.util.Locale; @@ -145,12 +143,6 @@ public final class CallLogDates { // of the string is not usually capitalized. For example, "Wednesdsay" in Uzbek is "chorshanba” // (not capitalized). To handle this issue we apply title casing to the start of the sentence so // that "chorshanba, 2016 may 25,20:02" becomes "Chorshanba, 2016 may 25,20:02". - // - // The ICU library was not available in Android until N, so we can only do this in N+ devices. - // Pre-N devices will still see incorrect capitalization in some languages. - if (VERSION.SDK_INT < VERSION_CODES.N) { - return value; - } // Using the ICU library is safer than just applying toUpperCase() on the first letter of the // word because in some languages, there can be multiple starting characters which should be diff --git a/java/com/android/dialer/compat/CompatUtils.java b/java/com/android/dialer/compat/CompatUtils.java index 584f20549..d09f8b0e1 100644 --- a/java/com/android/dialer/compat/CompatUtils.java +++ b/java/com/android/dialer/compat/CompatUtils.java @@ -16,17 +16,17 @@ package com.android.dialer.compat; import android.content.Context; -import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.LocaleList; import java.util.Locale; +/** TODO(calderwoodra): documentation */ public final class CompatUtils { /** PrioritizedMimeType is added in API level 23. */ public static boolean hasPrioritizedMimeType() { - return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) >= Build.VERSION_CODES.M; + return true; } /** @@ -36,8 +36,7 @@ public final class CompatUtils { * @return {@code true} if multi-SIM capability is available, {@code false} otherwise. */ public static boolean isMSIMCompatible() { - return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.LOLLIPOP) - >= Build.VERSION_CODES.LOLLIPOP_MR1; + return true; } /** @@ -47,7 +46,7 @@ public final class CompatUtils { * @return {@code true} if video calling is allowed, {@code false} otherwise. */ public static boolean isVideoCompatible() { - return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.LOLLIPOP) >= Build.VERSION_CODES.M; + return true; } /** @@ -57,7 +56,7 @@ public final class CompatUtils { * @return {@code true} if video presence checking is allowed, {@code false} otherwise. */ public static boolean isVideoPresenceCompatible() { - return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) > Build.VERSION_CODES.M; + return true; } /** @@ -67,7 +66,7 @@ public final class CompatUtils { * @return {@code true} if call subject is a feature on this device, {@code false} otherwise. */ public static boolean isCallSubjectCompatible() { - return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.LOLLIPOP) >= Build.VERSION_CODES.M; + return true; } /** Returns locale of the device. */ diff --git a/java/com/android/dialer/compat/SdkVersionOverride.java b/java/com/android/dialer/compat/SdkVersionOverride.java deleted file mode 100644 index 1d253a355..000000000 --- a/java/com/android/dialer/compat/SdkVersionOverride.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2015 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.dialer.compat; - -import android.os.Build.VERSION; - -/** - * Class used to override the current sdk version to test specific branches of compatibility logic. - * When such branching occurs, use {@link #getSdkVersion(int)} rather than explicitly calling {@link - * VERSION#SDK_INT}. This allows the sdk version to be forced to a specific value. - */ -public class SdkVersionOverride { - - /** Flag used to determine if override sdk versions are returned. */ - private static final boolean ALLOW_OVERRIDE_VERSION = false; - - private SdkVersionOverride() {} - - /** - * Gets the sdk version - * - * @param overrideVersion the version to attempt using - * @return overrideVersion if the {@link #ALLOW_OVERRIDE_VERSION} flag is set to {@code true}, - * otherwise the current version - */ - public static int getSdkVersion(int overrideVersion) { - return ALLOW_OVERRIDE_VERSION ? overrideVersion : VERSION.SDK_INT; - } -} diff --git a/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java b/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java index c4ed6e6ed..655540bba 100644 --- a/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java +++ b/java/com/android/dialer/compat/telephony/TelephonyManagerCompat.java @@ -25,7 +25,6 @@ import android.support.annotation.Nullable; import android.support.v4.os.BuildCompat; import android.telecom.PhoneAccountHandle; import android.telephony.TelephonyManager; -import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.telecom.TelecomUtil; import java.lang.reflect.InvocationTargetException; @@ -133,9 +132,6 @@ public class TelephonyManagerCompat { @Nullable public static Uri getVoicemailRingtoneUri( TelephonyManager telephonyManager, PhoneAccountHandle accountHandle) { - if (VERSION.SDK_INT < VERSION_CODES.N) { - return null; - } return telephonyManager.getVoicemailRingtoneUri(accountHandle); } @@ -149,8 +145,7 @@ public class TelephonyManagerCompat { */ public static boolean isVoicemailVibrationEnabled( TelephonyManager telephonyManager, PhoneAccountHandle accountHandle) { - return VERSION.SDK_INT < VERSION_CODES.N - || telephonyManager.isVoicemailVibrationEnabled(accountHandle); + return telephonyManager.isVoicemailVibrationEnabled(accountHandle); } /** @@ -159,9 +154,6 @@ public class TelephonyManagerCompat { */ public static void setVisualVoicemailEnabled( TelephonyManager telephonyManager, PhoneAccountHandle handle, boolean enabled) { - if (VERSION.SDK_INT < VERSION_CODES.N_MR1) { - Assert.fail("setVisualVoicemailEnabled called on pre-NMR1"); - } try { TelephonyManager.class .getMethod("setVisualVoicemailEnabled", PhoneAccountHandle.class, boolean.class) @@ -177,9 +169,6 @@ public class TelephonyManagerCompat { */ public static boolean isVisualVoicemailEnabled( TelephonyManager telephonyManager, PhoneAccountHandle handle) { - if (VERSION.SDK_INT < VERSION_CODES.N_MR1) { - Assert.fail("isVisualVoicemailEnabled called on pre-NMR1"); - } try { return (boolean) TelephonyManager.class diff --git a/java/com/android/dialer/database/CallLogQueryHandler.java b/java/com/android/dialer/database/CallLogQueryHandler.java index 92c49a09e..e974cc48f 100644 --- a/java/com/android/dialer/database/CallLogQueryHandler.java +++ b/java/com/android/dialer/database/CallLogQueryHandler.java @@ -26,7 +26,6 @@ import android.database.sqlite.SQLiteDiskIOException; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteFullException; import android.net.Uri; -import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -36,7 +35,6 @@ import android.provider.VoicemailContract.Voicemails; import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler; import com.android.dialer.common.LogUtil; import com.android.dialer.compat.AppCompatConstants; -import com.android.dialer.compat.SdkVersionOverride; import com.android.dialer.phonenumbercache.CallLogQuery; import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.PermissionsUtil; @@ -157,9 +155,7 @@ public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler { selectionArgs.add(Integer.toString(AppCompatConstants.CALLS_BLOCKED_TYPE)); // Ignore voicemails marked as deleted - if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) >= Build.VERSION_CODES.M) { - where.append(" AND (").append(Voicemails.DELETED).append(" = 0)"); - } + where.append(" AND (").append(Voicemails.DELETED).append(" = 0)"); if (newOnly) { where.append(" AND (").append(Calls.NEW).append(" = 1)"); diff --git a/java/com/android/dialer/oem/CequintCallerIdManager.java b/java/com/android/dialer/oem/CequintCallerIdManager.java index 48a5985ce..ee865bc14 100644 --- a/java/com/android/dialer/oem/CequintCallerIdManager.java +++ b/java/com/android/dialer/oem/CequintCallerIdManager.java @@ -41,7 +41,7 @@ import java.util.concurrent.ConcurrentHashMap; * Cequint Caller ID. It also caches any information fetched in static map, which lives through * whole application lifecycle. */ -@TargetApi(VERSION_CODES.M) +@TargetApi(VERSION_CODES.N) public class CequintCallerIdManager { private static final String CONFIG_CALLER_ID_ENABLED = "config_caller_id_enabled"; diff --git a/java/com/android/dialer/p13n/inference/P13nRanking.java b/java/com/android/dialer/p13n/inference/P13nRanking.java deleted file mode 100644 index 79b4d7136..000000000 --- a/java/com/android/dialer/p13n/inference/P13nRanking.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2016 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.dialer.p13n.inference; - -import android.content.Context; -import android.database.Cursor; -import android.support.annotation.MainThread; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import com.android.dialer.common.Assert; -import com.android.dialer.configprovider.ConfigProviderBindings; -import com.android.dialer.p13n.inference.protocol.P13nRanker; -import com.android.dialer.p13n.inference.protocol.P13nRankerFactory; -import java.util.List; - -/** Single entry point for all personalized ranking. */ -public final class P13nRanking { - - private static P13nRanker ranker; - - private P13nRanking() {} - - @MainThread - @NonNull - public static P13nRanker get(@NonNull Context context) { - Assert.isNotNull(context); - Assert.isMainThread(); - - if (ranker != null) { - return ranker; - } - - if (!ConfigProviderBindings.get(context).getBoolean("p13n_ranker_should_enable", false)) { - setToIdentityRanker(); - return ranker; - } - - Context application = context.getApplicationContext(); - if (application instanceof P13nRankerFactory) { - ranker = ((P13nRankerFactory) application).newP13nRanker(); - } - - if (ranker == null) { - setToIdentityRanker(); - } - return ranker; - } - - private static void setToIdentityRanker() { - ranker = - new P13nRanker() { - @Override - public void refresh(@Nullable P13nRefreshCompleteListener listener) {} - - @Override - public List<String> rankList(List<String> phoneNumbers) { - return phoneNumbers; - } - - @NonNull - @Override - public Cursor rankCursor(@NonNull Cursor phoneQueryResults, int queryLength) { - return phoneQueryResults; - } - - @Override - public boolean shouldShowEmptyListForNullQuery() { - return true; - } - }; - } - - public static void setForTesting(@NonNull P13nRanker ranker) { - P13nRanking.ranker = ranker; - } -} diff --git a/java/com/android/dialer/p13n/inference/protocol/P13nRanker.java b/java/com/android/dialer/p13n/inference/protocol/P13nRanker.java deleted file mode 100644 index 41f1de49d..000000000 --- a/java/com/android/dialer/p13n/inference/protocol/P13nRanker.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2016 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.dialer.p13n.inference.protocol; - -import android.database.Cursor; -import android.support.annotation.MainThread; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import java.util.List; - -/** Provides personalized ranking of outgoing call targets. */ -public interface P13nRanker { - - /** - * Re-orders a list of phone numbers according to likelihood they will be the next outgoing call. - * - * @param phoneNumbers the list of candidate numbers to call (may be in contacts list or not) - */ - @NonNull - @MainThread - List<String> rankList(@NonNull List<String> phoneNumbers); - - /** - * Re-orders a retrieved contact list according to likelihood they will be the next outgoing call. - * - * <p>A new cursor with reordered data is returned; the input cursor is unmodified except for its - * position. If the order is unchanged, this method may return a reference to the unmodified input - * cursor directly. The order would be unchanged if the ranking cache is not yet ready, or if the - * input cursor is closed or invalid, or if any other error occurs in the ranking process. - * - * @param phoneQueryResults cursor of results of a Dialer search query - * @param queryLength length of the search query that resulted in the cursor data, if below 0, - * assumes no length is specified, thus applies the default behavior which is same as when - * queryLength is greater than zero. - * @return new cursor of data reordered by ranking (or reference to input cursor if order - * unchanged) - */ - @NonNull - @MainThread - Cursor rankCursor(@NonNull Cursor phoneQueryResults, int queryLength); - - /** - * Refreshes ranking cache (pulls fresh contextual features, pre-caches inference results, etc.). - * - * <p>Asynchronously runs in background as the process might take a few seconds, notifying a - * listener upon completion; meanwhile, any calls to {@link #rankList} will simply return the - * input in same order. - * - * @param listener callback for when ranking refresh has completed; null value skips notification. - */ - @MainThread - void refresh(@Nullable P13nRefreshCompleteListener listener); - - /** Decides if results should be displayed for no-query search. */ - @MainThread - boolean shouldShowEmptyListForNullQuery(); - - /** - * Callback class for when ranking refresh has completed. - * - * <p>Primary use is to notify {@link com.android.dialer.app.DialtactsActivity} that the ranking - * functions {@link #rankList} and {@link #rankCursor(Cursor, int)} will now give useful results. - */ - interface P13nRefreshCompleteListener { - - /** Callback for when ranking refresh has completed. */ - void onP13nRefreshComplete(); - } -} diff --git a/java/com/android/dialer/p13n/inference/protocol/P13nRankerFactory.java b/java/com/android/dialer/p13n/inference/protocol/P13nRankerFactory.java deleted file mode 100644 index 7038cf456..000000000 --- a/java/com/android/dialer/p13n/inference/protocol/P13nRankerFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2016 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.dialer.p13n.inference.protocol; - -import android.support.annotation.Nullable; - -/** - * This interface should be implemented by the Application subclass. It allows this module to get - * references to the {@link P13nRanker}. - */ -public interface P13nRankerFactory { - @Nullable - P13nRanker newP13nRanker(); -} diff --git a/java/com/android/dialer/p13n/logging/P13nLogger.java b/java/com/android/dialer/p13n/logging/P13nLogger.java deleted file mode 100644 index 069a29328..000000000 --- a/java/com/android/dialer/p13n/logging/P13nLogger.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2016 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.dialer.p13n.logging; - -import com.android.contacts.common.list.PhoneNumberListAdapter; - -/** Allows logging of data for personalization. */ -public interface P13nLogger { - - /** - * Logs a search query (text or digits) entered by user. - * - * @param query search text (or digits) entered by user - * @param adapter list adapter providing access to contacts matching search query - */ - void onSearchQuery(String query, PhoneNumberListAdapter adapter); - - /** - * Resets logging session (clears searches, re-initializes app entry timestamp, etc.) Should be - * called when Dialer app is resumed. - */ - void reset(); -} diff --git a/java/com/android/dialer/p13n/logging/P13nLoggerFactory.java b/java/com/android/dialer/p13n/logging/P13nLoggerFactory.java deleted file mode 100644 index 7350e99e1..000000000 --- a/java/com/android/dialer/p13n/logging/P13nLoggerFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2016 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.dialer.p13n.logging; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -/** - * This interface should be implemented by the Application subclass. It allows this module to get - * references to the P13nLogger. - */ -public interface P13nLoggerFactory { - - @Nullable - P13nLogger newP13nLogger(@NonNull Context context); -} diff --git a/java/com/android/dialer/p13n/logging/P13nLogging.java b/java/com/android/dialer/p13n/logging/P13nLogging.java deleted file mode 100644 index 21b97257b..000000000 --- a/java/com/android/dialer/p13n/logging/P13nLogging.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2016 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.dialer.p13n.logging; - -import android.content.Context; -import android.support.annotation.NonNull; -import com.android.contacts.common.list.PhoneNumberListAdapter; -import com.android.dialer.common.Assert; - -/** Single entry point for all logging for personalization. */ -public final class P13nLogging { - - private static P13nLogger logger; - - private P13nLogging() {} - - @NonNull - public static P13nLogger get(@NonNull Context context) { - Assert.isNotNull(context); - Assert.isMainThread(); - if (logger != null) { - return logger; - } - - Context application = context.getApplicationContext(); - if (application instanceof P13nLoggerFactory) { - logger = ((P13nLoggerFactory) application).newP13nLogger(context); - } - - if (logger == null) { - logger = - new P13nLogger() { - @Override - public void onSearchQuery(String query, PhoneNumberListAdapter adapter) {} - - @Override - public void reset() {} - }; - } - return logger; - } - - public static void setForTesting(@NonNull P13nLogger logger) { - P13nLogging.logger = logger; - } -} diff --git a/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java b/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java index a3d51a78f..fe6642eef 100644 --- a/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java +++ b/java/com/android/dialer/phonelookup/blockednumber/SystemBlockedNumberPhoneLookup.java @@ -16,11 +16,8 @@ package com.android.dialer.phonelookup.blockednumber; -import android.annotation.TargetApi; import android.content.Context; import android.database.Cursor; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.provider.BlockedNumberContract.BlockedNumbers; import android.support.annotation.NonNull; import android.support.annotation.WorkerThread; @@ -72,10 +69,7 @@ public class SystemBlockedNumberPhoneLookup implements PhoneLookup<SystemBlocked if (!FilteredNumberCompat.useNewFiltering(appContext)) { return Futures.immediateFuture(SystemBlockedNumberInfo.getDefaultInstance()); } - return executorService.submit( - () -> { - return queryNumbers(ImmutableSet.of(number)).get(number); - }); + return executorService.submit(() -> queryNumbers(ImmutableSet.of(number)).get(number)); } @Override @@ -96,7 +90,6 @@ public class SystemBlockedNumberPhoneLookup implements PhoneLookup<SystemBlocked } @WorkerThread - @TargetApi(VERSION_CODES.N) private ImmutableMap<DialerPhoneNumber, SystemBlockedNumberInfo> queryNumbers( ImmutableSet<DialerPhoneNumber> numbers) { Assert.isWorkerThread(); @@ -171,9 +164,6 @@ public class SystemBlockedNumberPhoneLookup implements PhoneLookup<SystemBlocked @Override public void registerContentObservers() { - if (VERSION.SDK_INT < VERSION_CODES.N) { - return; - } appContext .getContentResolver() .registerContentObserver( diff --git a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java index 01f9669cb..d6e378cf2 100644 --- a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java +++ b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java @@ -14,14 +14,11 @@ package com.android.dialer.phonenumbercache; -import android.annotation.TargetApi; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteFullException; import android.net.Uri; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.provider.CallLog.Calls; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Phone; @@ -53,8 +50,6 @@ import org.json.JSONException; import org.json.JSONObject; /** Utility class to look up the contact information for a given number. */ -// This class uses Java 7 language features, so it must target M+ -@TargetApi(VERSION_CODES.M) public class ContactInfoHelper { private static final String TAG = ContactInfoHelper.class.getSimpleName(); @@ -153,15 +148,6 @@ public class ContactInfoHelper { // Get URI for the number in the PhoneLookup table, with a parameter to indicate whether // the number is a SIP number. Uri uri = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI; - if (VERSION.SDK_INT < VERSION_CODES.N) { - if (directoryId != -1) { - // ENTERPRISE_CONTENT_FILTER_URI in M doesn't support directory lookup - uri = PhoneLookup.CONTENT_FILTER_URI; - } else { - // a bug in M. PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, encodes twice. - number = Uri.encode(number); - } - } Uri.Builder builder = uri.buildUpon() .appendPath(number) @@ -187,8 +173,7 @@ public class ContactInfoHelper { info.type = c.getInt(CallLogQuery.CACHED_NUMBER_TYPE); info.label = c.getString(CallLogQuery.CACHED_NUMBER_LABEL); String matchedNumber = c.getString(CallLogQuery.CACHED_MATCHED_NUMBER); - String postDialDigits = - (VERSION.SDK_INT >= VERSION_CODES.N) ? c.getString(CallLogQuery.POST_DIAL_DIGITS) : ""; + String postDialDigits = c.getString(CallLogQuery.POST_DIAL_DIGITS); info.number = (matchedNumber == null) ? c.getString(CallLogQuery.NUMBER) + postDialDigits : matchedNumber; @@ -294,10 +279,7 @@ public class ContactInfoHelper { private List<Long> getRemoteDirectories(Context context) { List<Long> remoteDirectories = new ArrayList<>(); - Uri uri = - VERSION.SDK_INT >= VERSION_CODES.N - ? Directory.ENTERPRISE_CONTENT_URI - : Directory.CONTENT_URI; + Uri uri = Directory.ENTERPRISE_CONTENT_URI; Cursor cursor = context.getContentResolver().query(uri, new String[] {Directory._ID}, null, null, null); if (cursor == null) { diff --git a/java/com/android/dialer/preferredsim/PreferredAccountWorker.java b/java/com/android/dialer/preferredsim/PreferredAccountWorker.java index bfaaa7cde..aa617889e 100644 --- a/java/com/android/dialer/preferredsim/PreferredAccountWorker.java +++ b/java/com/android/dialer/preferredsim/PreferredAccountWorker.java @@ -24,8 +24,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.net.Uri; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.PhoneLookup; @@ -130,9 +128,6 @@ public class PreferredAccountWorker implements Worker<Context, Result> { private static Optional<String> getDataId( @NonNull Context context, @Nullable String phoneNumber) { Assert.isWorkerThread(); - if (VERSION.SDK_INT < VERSION_CODES.N) { - return Optional.absent(); - } try (Cursor cursor = context .getContentResolver() diff --git a/java/com/android/dialer/shortcuts/AndroidManifest.xml b/java/com/android/dialer/shortcuts/AndroidManifest.xml index 117005841..36f61feff 100644 --- a/java/com/android/dialer/shortcuts/AndroidManifest.xml +++ b/java/com/android/dialer/shortcuts/AndroidManifest.xml @@ -17,7 +17,7 @@ package="com.android.dialer.shortcuts"> <uses-sdk - android:minSdkVersion="23" + android:minSdkVersion="24" android:targetSdkVersion="27"/> <application android:theme="@style/Theme.AppCompat"> diff --git a/java/com/android/dialer/smartdial/SmartDialCursorLoader.java b/java/com/android/dialer/smartdial/SmartDialCursorLoader.java index f2e41b22b..205362c14 100644 --- a/java/com/android/dialer/smartdial/SmartDialCursorLoader.java +++ b/java/com/android/dialer/smartdial/SmartDialCursorLoader.java @@ -20,7 +20,7 @@ import android.content.AsyncTaskLoader; import android.content.Context; import android.database.Cursor; import android.database.MatrixCursor; -import com.android.contacts.common.list.PhoneNumberListAdapter.PhoneQuery; +import android.provider.ContactsContract.CommonDataKinds.Phone; import com.android.dialer.common.LogUtil; import com.android.dialer.database.Database; import com.android.dialer.database.DialerDatabaseHelper; @@ -28,6 +28,8 @@ import com.android.dialer.database.DialerDatabaseHelper.ContactNumber; import com.android.dialer.smartdial.util.SmartDialNameMatcher; import com.android.dialer.util.PermissionsUtil; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** Implements a Loader<Cursor> class to asynchronously load SmartDial search results. */ public class SmartDialCursorLoader extends AsyncTaskLoader<Cursor> { @@ -179,4 +181,60 @@ public class SmartDialCursorLoader extends AsyncTaskLoader<Cursor> { nameMatcher.setShouldMatchEmptyQuery(!show); } } + + /** Moved from contacts/common, contains all of the projections needed for Smart Dial queries. */ + public static class PhoneQuery { + + 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/dialer/spannable/AndroidManifest.xml b/java/com/android/dialer/spannable/AndroidManifest.xml index e256dc1bb..5fa6407f7 100644 --- a/java/com/android/dialer/spannable/AndroidManifest.xml +++ b/java/com/android/dialer/spannable/AndroidManifest.xml @@ -17,6 +17,6 @@ package="com.android.dialer.spannable"> <uses-sdk - android:minSdkVersion="23" + android:minSdkVersion="24" android:targetSdkVersion="27"/> </manifest> diff --git a/java/com/android/dialer/voicemail/listui/error/VoicemailStatusWorker.java b/java/com/android/dialer/voicemail/listui/error/VoicemailStatusWorker.java index df58d419f..e87dee3b0 100644 --- a/java/com/android/dialer/voicemail/listui/error/VoicemailStatusWorker.java +++ b/java/com/android/dialer/voicemail/listui/error/VoicemailStatusWorker.java @@ -32,7 +32,7 @@ import java.util.List; /** * Worker for {@link com.android.dialer.common.concurrent.DialerExecutors} to fetch voicemail status */ -@TargetApi(VERSION_CODES.M) +@TargetApi(VERSION_CODES.N) public class VoicemailStatusWorker implements Worker<Context, List<VoicemailStatus>> { @Nullable diff --git a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java index 2e76b70fe..007ab202d 100644 --- a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java +++ b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java @@ -75,7 +75,7 @@ public class VoicemailSettingsFragment extends PreferenceFragment // Settings that are supported by dialer only if the carrier configurations are valid. private SwitchPreference visualVoicemailPreference; private SwitchPreference voicemailAutoArchivePreference; - private SwitchPreference transcribeVoicemailPreference; + private SwitchPreferenceWithClickableSummary transcribeVoicemailPreference; // Voicemail transcription analysis toggle private SwitchPreferenceWithClickableSummary donateTranscribedVoicemailPreference; private Preference voicemailChangePinPreference; @@ -148,12 +148,24 @@ public class VoicemailSettingsFragment extends PreferenceFragment transcribeVoicemailPreference.setOnPreferenceChangeListener(this); transcribeVoicemailPreference.setChecked( voicemailClient.isVoicemailTranscriptionEnabled(getContext(), phoneAccountHandle)); - transcribeVoicemailPreference.setSummary( - R.string.voicemail_transcription_preference_summary_info); + transcribeVoicemailPreference.setSummary(getVoicemailTranscriptionInformationalText()); transcribeVoicemailPreference.setEnabled(true); getPreferenceScreen().addPreference(transcribeVoicemailPreference); } + /** + * Builds a spannable string containing the voicemail transcription informational text containing + * the appropriate "Learn More" urls. + * + * @return The voicemail transcription information text. + */ + private CharSequence getVoicemailTranscriptionInformationalText() { + return new ContentWithLearnMoreSpanner(getContext()) + .create( + getContext().getString(R.string.voicemail_transcription_preference_summary_info), + getContext().getString(R.string.transcription_learn_more_url)); + } + private void updateTranscriptionDonationPreference() { if (!VoicemailComponent.get(getContext()) .getVoicemailClient() @@ -231,7 +243,7 @@ public class VoicemailSettingsFragment extends PreferenceFragment voicemailAutoArchivePreference.setOrder(VMSettingOrdering.VOICEMAIL_AUTO_ARCHIVE); transcribeVoicemailPreference = - (SwitchPreference) + (SwitchPreferenceWithClickableSummary) findPreference(getString(R.string.voicemail_visual_voicemail_transcription_key)); transcribeVoicemailPreference.setOrder(VMSettingOrdering.VOICEMAIL_TRANSCRIPTION); diff --git a/java/com/android/dialer/voicemail/settings/res/values/strings.xml b/java/com/android/dialer/voicemail/settings/res/values/strings.xml index 7df8a0192..ad245ee47 100644 --- a/java/com/android/dialer/voicemail/settings/res/values/strings.xml +++ b/java/com/android/dialer/voicemail/settings/res/values/strings.xml @@ -119,7 +119,7 @@ <string name="voicemail_activating_summary_info">Activating voicemail</string> <!-- Summary information for visual voicemail transcription setting [CHAR LIMIT=NONE] --> - <string name="voicemail_transcription_preference_summary_info">Get transcripts of your voicemail using Google\'s transcription service.</string> + <string name="voicemail_transcription_preference_summary_info">Get transcripts of your voicemail using Google\'s transcription service. <xliff:g example="Learn more">%1$s</xliff:g></string> <!-- Summary information for visual voicemail donation setting [CHAR LIMIT=NONE] --> <string name="voicemail_donate_preference_summary_info">Let Google review your voicemail messages to improve transcription accuracy. Your voicemail messages are stored anonymously. <xliff:g example="Learn more">%1$s</xliff:g></string> diff --git a/java/com/android/dialer/voicemail/settings/res/xml/voicemail_settings.xml b/java/com/android/dialer/voicemail/settings/res/xml/voicemail_settings.xml index e5af813b6..fc839ee3e 100644 --- a/java/com/android/dialer/voicemail/settings/res/xml/voicemail_settings.xml +++ b/java/com/android/dialer/voicemail/settings/res/xml/voicemail_settings.xml @@ -31,10 +31,11 @@ android:key="@string/voicemail_visual_voicemail_archive_key" android:title="@string/voicemail_visual_voicemail_auto_archive_switch_title"/>" - <SwitchPreference + <com.android.dialer.common.preference.SwitchPreferenceWithClickableSummary android:dependency="@string/voicemail_visual_voicemail_key" android:key="@string/voicemail_visual_voicemail_transcription_key" - android:title="@string/voicemail_visual_voicemail_transcription_switch_title"/>" + android:title="@string/voicemail_visual_voicemail_transcription_switch_title" + app:urlToOpen="@string/transcription_learn_more_url"/> <com.android.dialer.common.preference.SwitchPreferenceWithClickableSummary android:dependency="@string/voicemail_visual_voicemail_transcription_key" diff --git a/java/com/android/incallui/AndroidManifest.xml b/java/com/android/incallui/AndroidManifest.xml index 24177979e..9a762feea 100644 --- a/java/com/android/incallui/AndroidManifest.xml +++ b/java/com/android/incallui/AndroidManifest.xml @@ -18,7 +18,7 @@ package="com.android.incallui"> <uses-sdk - android:minSdkVersion="23" + android:minSdkVersion="24" android:targetSdkVersion="27"/> <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/> diff --git a/java/com/android/incallui/CallerInfoAsyncQuery.java b/java/com/android/incallui/CallerInfoAsyncQuery.java index 87dcc4f40..4170cc318 100644 --- a/java/com/android/incallui/CallerInfoAsyncQuery.java +++ b/java/com/android/incallui/CallerInfoAsyncQuery.java @@ -53,7 +53,7 @@ import java.util.Arrays; * * @see CallerInfo */ -@TargetApi(VERSION_CODES.M) +@TargetApi(VERSION_CODES.N) public class CallerInfoAsyncQuery { /** Interface for a CallerInfoAsyncQueryHandler result return. */ diff --git a/java/com/android/incallui/answer/impl/hint/PawImageLoaderImpl.java b/java/com/android/incallui/answer/impl/hint/PawImageLoaderImpl.java index 31b17112b..aef56872a 100644 --- a/java/com/android/incallui/answer/impl/hint/PawImageLoaderImpl.java +++ b/java/com/android/incallui/answer/impl/hint/PawImageLoaderImpl.java @@ -29,7 +29,7 @@ import com.android.dialer.storage.StorageComponent; import com.android.incallui.answer.impl.hint.PawSecretCodeListener.PawType; /** Decrypt the event payload to be shown if in a specific time range and the key is received. */ -@TargetApi(VERSION_CODES.M) +@TargetApi(VERSION_CODES.N) public final class PawImageLoaderImpl implements PawImageLoader { @Override diff --git a/java/com/android/incallui/autoresizetext/AndroidManifest.xml b/java/com/android/incallui/autoresizetext/AndroidManifest.xml index e26670e52..1ba8b7c04 100644 --- a/java/com/android/incallui/autoresizetext/AndroidManifest.xml +++ b/java/com/android/incallui/autoresizetext/AndroidManifest.xml @@ -18,7 +18,7 @@ package="com.android.incallui.autoresizetext"> <uses-sdk - android:minSdkVersion="23" + android:minSdkVersion="24" android:targetSdkVersion="27"/> <application /> diff --git a/java/com/android/incallui/legacyblocking/DeleteBlockedCallTask.java b/java/com/android/incallui/legacyblocking/DeleteBlockedCallTask.java index a3f2dfa4d..f9fe6a68a 100644 --- a/java/com/android/incallui/legacyblocking/DeleteBlockedCallTask.java +++ b/java/com/android/incallui/legacyblocking/DeleteBlockedCallTask.java @@ -34,7 +34,7 @@ import java.util.Objects; * versions of the OS, call blocking is implemented in the system and there's no need to mess with * the call log. */ -@TargetApi(VERSION_CODES.M) +@TargetApi(VERSION_CODES.N) public class DeleteBlockedCallTask extends AsyncTask<Void, Void, Long> { public static final String IDENTIFIER = "DeleteBlockedCallTask"; diff --git a/java/com/android/incallui/rtt/protocol/AndroidManifest.xml b/java/com/android/incallui/rtt/protocol/AndroidManifest.xml index 52514a501..c0d39b091 100644 --- a/java/com/android/incallui/rtt/protocol/AndroidManifest.xml +++ b/java/com/android/incallui/rtt/protocol/AndroidManifest.xml @@ -17,6 +17,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.incallui.rtt.protocol"> <uses-sdk - android:minSdkVersion="23" + android:minSdkVersion="24" android:targetSdkVersion="26"/> </manifest>
\ No newline at end of file diff --git a/java/com/android/incallui/spam/SpamCallListListener.java b/java/com/android/incallui/spam/SpamCallListListener.java index 22b383332..9ef65d877 100644 --- a/java/com/android/incallui/spam/SpamCallListListener.java +++ b/java/com/android/incallui/spam/SpamCallListListener.java @@ -87,7 +87,7 @@ public class SpamCallListListener implements CallList.Listener { } /** Checks if the number is in the call history. */ - @TargetApi(VERSION_CODES.M) + @TargetApi(VERSION_CODES.N) private static final class NumberInCallHistoryWorker implements Worker<Void, Integer> { private final Context appContext; diff --git a/java/com/android/incallui/video/protocol/AndroidManifest.xml b/java/com/android/incallui/video/protocol/AndroidManifest.xml index 6f6558278..f059b191f 100644 --- a/java/com/android/incallui/video/protocol/AndroidManifest.xml +++ b/java/com/android/incallui/video/protocol/AndroidManifest.xml @@ -17,6 +17,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.incallui.video.protocol"> <uses-sdk - android:minSdkVersion="23" + android:minSdkVersion="24" android:targetSdkVersion="27"/> </manifest> diff --git a/java/com/android/incallui/videotech/ims/ImsVideoTech.java b/java/com/android/incallui/videotech/ims/ImsVideoTech.java index a2fb73bd2..d9660e192 100644 --- a/java/com/android/incallui/videotech/ims/ImsVideoTech.java +++ b/java/com/android/incallui/videotech/ims/ImsVideoTech.java @@ -17,7 +17,6 @@ package com.android.incallui.videotech.ims; import android.content.Context; -import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; @@ -61,10 +60,6 @@ public class ImsVideoTech implements VideoTech { @Override public boolean isAvailable(Context context, PhoneAccountHandle phoneAccountHandle) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return false; - } - if (call.getVideoCall() == null) { return false; } diff --git a/java/com/android/voicemail/AndroidManifest.xml b/java/com/android/voicemail/AndroidManifest.xml index d64fb2504..23c746a80 100644 --- a/java/com/android/voicemail/AndroidManifest.xml +++ b/java/com/android/voicemail/AndroidManifest.xml @@ -17,7 +17,7 @@ package="com.android.voicemail"> <uses-sdk - android:minSdkVersion="23" + android:minSdkVersion="24" android:targetSdkVersion="27"/> <!-- Applications using this module should merge these permissions using android_manifest_merge --> diff --git a/java/com/android/voicemail/impl/PackageReplacedReceiver.java b/java/com/android/voicemail/impl/PackageReplacedReceiver.java index bc56286fb..9fa9f75c7 100644 --- a/java/com/android/voicemail/impl/PackageReplacedReceiver.java +++ b/java/com/android/voicemail/impl/PackageReplacedReceiver.java @@ -91,7 +91,7 @@ public class PackageReplacedReceiver extends BroadcastReceiver { this.context = context; } - @TargetApi(android.os.Build.VERSION_CODES.M) // used for try with resources + @TargetApi(android.os.Build.VERSION_CODES.N) // used for try with resources @Override public Void doInBackground(Void arg) throws Throwable { LogUtil.i("PackageReplacedReceiver.ExistingVoicemailCheck.doInBackground", ""); diff --git a/java/com/android/voicemail/impl/sync/VoicemailsQueryHelper.java b/java/com/android/voicemail/impl/sync/VoicemailsQueryHelper.java index c47b40dab..be11c4453 100644 --- a/java/com/android/voicemail/impl/sync/VoicemailsQueryHelper.java +++ b/java/com/android/voicemail/impl/sync/VoicemailsQueryHelper.java @@ -256,7 +256,7 @@ public class VoicemailsQueryHelper { } /** Find the oldest voicemails that are on the device, and also on the server. */ - @TargetApi(VERSION_CODES.M) // used for try with resources + @TargetApi(VERSION_CODES.N) // used for try with resources public List<Voicemail> oldestVoicemailsOnServer(int numVoicemails) { if (numVoicemails <= 0) { Assert.fail("Query for remote voicemails cannot be <= 0"); diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java b/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java index 3bd14731f..d8c00ed91 100644 --- a/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java +++ b/java/com/android/voicemail/impl/transcribe/TranscriptionUtils.java @@ -34,7 +34,7 @@ public class TranscriptionUtils { static final String AMR_PREFIX = "#!AMR\n"; // Uses try-with-resource - @TargetApi(android.os.Build.VERSION_CODES.M) + @TargetApi(android.os.Build.VERSION_CODES.N) static ByteString getAudioData(Context context, Uri voicemailUri) { try (InputStream in = context.getContentResolver().openInputStream(voicemailUri)) { return ByteString.readFrom(in); |