diff options
88 files changed, 151 insertions, 9192 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 164dbc907..26fed40c5 100644 --- a/AndroidManifest.xml +++ b/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/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); |