diff options
author | Ricardo Cerqueira <cyanogenmod@cerqueira.org> | 2013-11-05 20:04:57 +0000 |
---|---|---|
committer | Ricardo Cerqueira <cyanogenmod@cerqueira.org> | 2013-11-05 20:04:57 +0000 |
commit | a42f690f2014b3588d67677c5f30859790600eb7 (patch) | |
tree | f5af06adc40a5a5c40377d060a9b0533b8e6cb09 /src/com/android/contacts/common | |
parent | d70b3939dcbaa1ca04a120b9c8aed00971824ac9 (diff) | |
parent | 1d562555a51937778a16d0f7f19a61bce48aaf1c (diff) | |
download | android_packages_apps_ContactsCommon-a42f690f2014b3588d67677c5f30859790600eb7.tar.gz android_packages_apps_ContactsCommon-a42f690f2014b3588d67677c5f30859790600eb7.tar.bz2 android_packages_apps_ContactsCommon-a42f690f2014b3588d67677c5f30859790600eb7.zip |
Merge tag 'android-4.4_r1' into cm-11.0
Android 4.4 Release 1.0
Diffstat (limited to 'src/com/android/contacts/common')
28 files changed, 957 insertions, 264 deletions
diff --git a/src/com/android/contacts/common/Collapser.java b/src/com/android/contacts/common/Collapser.java index 39ae681b..d1446711 100644 --- a/src/com/android/contacts/common/Collapser.java +++ b/src/com/android/contacts/common/Collapser.java @@ -33,6 +33,12 @@ public final class Collapser { private Collapser() {} /* + * The Collapser uses an n^2 algorithm so we don't want it to run on + * lists beyond a certain size. This specifies the maximum size to collapse. + */ + private static final int MAX_LISTSIZE_TO_COLLAPSE = 20; + + /* * Interface implemented by data types that can be collapsed into groups of similar data. This * can be used for example to collapse similar contact data items into a single item. */ @@ -51,6 +57,10 @@ public final class Collapser { public static <T extends Collapsible<T>> void collapseList(List<T> list) { int listSize = list.size(); + // The algorithm below is n^2 so don't run on long lists + if (listSize > MAX_LISTSIZE_TO_COLLAPSE) { + return; + } for (int i = 0; i < listSize; i++) { T iItem = list.get(i); diff --git a/src/com/android/contacts/common/ContactPhotoManager.java b/src/com/android/contacts/common/ContactPhotoManager.java index ea49d56e..995201d6 100644 --- a/src/com/android/contacts/common/ContactPhotoManager.java +++ b/src/com/android/contacts/common/ContactPhotoManager.java @@ -58,6 +58,7 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.lang.ref.Reference; import java.lang.ref.SoftReference; +import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -1109,7 +1110,13 @@ class ContactPhotoManagerImpl extends ContactPhotoManager implements Callback { } try { if (DEBUG) Log.d(TAG, "Loading " + uri); - InputStream is = mResolver.openInputStream(uri); + final String scheme = uri.getScheme(); + InputStream is = null; + if (scheme.equals("http") || scheme.equals("https")) { + is = new URL(uri.toString()).openStream(); + } else { + is = mResolver.openInputStream(uri); + } if (is != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { diff --git a/src/com/android/contacts/common/ContactTileLoaderFactory.java b/src/com/android/contacts/common/ContactTileLoaderFactory.java index 19eac769..2a44c097 100644 --- a/src/com/android/contacts/common/ContactTileLoaderFactory.java +++ b/src/com/android/contacts/common/ContactTileLoaderFactory.java @@ -40,6 +40,12 @@ public final class ContactTileLoaderFactory { public final static int PHONE_NUMBER = 5; public final static int PHONE_NUMBER_TYPE = 6; public final static int PHONE_NUMBER_LABEL = 7; + public final static int IS_DEFAULT_NUMBER = 8; + public final static int PINNED = 9; + // The _ID field returned for strequent items actually contains data._id instead of + // contacts._id because the query is performed on the data table. In order to obtain the + // contact id for strequent items, we thus have to use Phone.contact_id instead. + public final static int CONTACT_ID_FOR_DATA = 10; private static final String[] COLUMNS = new String[] { Contacts._ID, // ..........................................0 @@ -65,7 +71,10 @@ public final class ContactTileLoaderFactory { Contacts.LOOKUP_KEY, // ...................................4 Phone.NUMBER, // ..........................................5 Phone.TYPE, // ............................................6 - Phone.LABEL // ............................................7 + Phone.LABEL, // ...........................................7 + Phone.IS_SUPER_PRIMARY, //.................................8 + Contacts.PINNED, // .......................................9 + Phone.CONTACT_ID //........................................10 }; private static final String STARRED_ORDER = Contacts.DISPLAY_NAME+" COLLATE NOCASE ASC"; diff --git a/src/com/android/contacts/common/GeoUtil.java b/src/com/android/contacts/common/GeoUtil.java index 335d316f..5ca04b0c 100644 --- a/src/com/android/contacts/common/GeoUtil.java +++ b/src/com/android/contacts/common/GeoUtil.java @@ -17,8 +17,16 @@ package com.android.contacts.common; import android.content.Context; +import android.location.Country; import android.location.CountryDetector; +import com.android.i18n.phonenumbers.NumberParseException; +import com.android.i18n.phonenumbers.PhoneNumberUtil; +import com.android.i18n.phonenumbers.Phonenumber; +import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder; + +import java.util.Locale; + /** * Static methods related to Geo. */ @@ -28,9 +36,31 @@ public class GeoUtil { * @return The ISO 3166-1 two letters country code of the country the user * is in. */ - public static final String getCurrentCountryIso(Context context) { - CountryDetector detector = + public static String getCurrentCountryIso(Context context) { + final CountryDetector detector = + (CountryDetector) context.getSystemService(Context.COUNTRY_DETECTOR); + if (detector != null) { + final Country country = detector.detectCountry(); + if (country != null) { + return country.getCountryIso(); + } + } + // Fallback to Locale if have issues with CountryDetector + return Locale.getDefault().getCountry(); + } + + public static String getGeocodedLocationFor(Context context, String phoneNumber) { + final PhoneNumberOfflineGeocoder geocoder = PhoneNumberOfflineGeocoder.getInstance(); + final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); + final CountryDetector countryDetector = (CountryDetector) context.getSystemService(Context.COUNTRY_DETECTOR); - return detector.detectCountry().getCountryIso(); + try { + final Phonenumber.PhoneNumber structuredPhoneNumber = + phoneNumberUtil.parse(phoneNumber, getCurrentCountryIso(context)); + final Locale locale = context.getResources().getConfiguration().locale; + return geocoder.getDescriptionForNumber(structuredPhoneNumber, locale); + } catch (NumberParseException e) { + return null; + } } } diff --git a/src/com/android/contacts/common/MoreContactUtils.java b/src/com/android/contacts/common/MoreContactUtils.java index 32a9d7ce..accd73c9 100644 --- a/src/com/android/contacts/common/MoreContactUtils.java +++ b/src/com/android/contacts/common/MoreContactUtils.java @@ -190,6 +190,7 @@ public class MoreContactUtils { View view = View.inflate(context, R.layout.list_separator, null); TextView textView = (TextView) view.findViewById(R.id.title); textView.setText(context.getString(textResourceId)); + textView.setAllCaps(true); return view; } diff --git a/src/com/android/contacts/common/extensions/ExtendedPhoneDirectoriesManager.java b/src/com/android/contacts/common/extensions/ExtendedPhoneDirectoriesManager.java new file mode 100644 index 00000000..eb259343 --- /dev/null +++ b/src/com/android/contacts/common/extensions/ExtendedPhoneDirectoriesManager.java @@ -0,0 +1,26 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +package com.android.contacts.common.extensions; + +import android.content.Context; + +import com.android.contacts.common.list.DirectoryPartition; + +import java.util.List; + +/** + * An interface for adding extended phone directories to + * {@link com.android.contacts.common.list.PhoneNumberListAdapter}. + * An app that wishes to add custom phone directories should implement this class and advertise it + * in assets/contacts_extensions.properties. {@link ExtensionsFactory} will load the implementation + * and the extended directories will be added by + * {@link com.android.contacts.common.list.PhoneNumberListAdapter}. + */ +public interface ExtendedPhoneDirectoriesManager { + + /** + * Return a list of extended directories to add. May return null if no directories are to be + * added. + */ + List<DirectoryPartition> getExtendedDirectories(Context context); +} diff --git a/src/com/android/contacts/common/extensions/ExtensionsFactory.java b/src/com/android/contacts/common/extensions/ExtensionsFactory.java new file mode 100644 index 00000000..d52429e6 --- /dev/null +++ b/src/com/android/contacts/common/extensions/ExtensionsFactory.java @@ -0,0 +1,89 @@ +/* + * 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.contacts.common.extensions; + +import android.content.Context; +import android.util.Log; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + + +/* + * A framework for adding extensions to Dialer. This class reads a property file from + * assets/contacts_extensions.properties and loads extension classes that an app has defined. If + * an extension class was not defined, null is returned. + */ +public class ExtensionsFactory { + + private static String TAG = "ExtensionsFactory"; + + // Config filename for mappings of various class names to their custom + // implementations. + private static final String EXTENSIONS_PROPERTIES = "contacts_extensions.properties"; + + private static final String EXTENDED_PHONE_DIRECTORIES_KEY = "extendedPhoneDirectories"; + + private static Properties sProperties = null; + private static ExtendedPhoneDirectoriesManager mExtendedPhoneDirectoriesManager = null; + + public static void init(Context context) { + if (sProperties != null) { + return; + } + try { + final InputStream fileStream = context.getAssets().open(EXTENSIONS_PROPERTIES); + sProperties = new Properties(); + sProperties.load(fileStream); + fileStream.close(); + + final String className = sProperties.getProperty(EXTENDED_PHONE_DIRECTORIES_KEY); + if (className != null) { + mExtendedPhoneDirectoriesManager = createInstance(className); + } else { + Log.d(TAG, EXTENDED_PHONE_DIRECTORIES_KEY + " not found in properties file."); + } + + } catch (FileNotFoundException e) { + // No custom extensions. Ignore. + Log.d(TAG, "No custom extensions."); + } catch (IOException e) { + Log.d(TAG, e.toString()); + } + } + + private static <T> T createInstance(String className) { + try { + Class<?> c = Class.forName(className); + //noinspection unchecked + return (T) c.newInstance(); + } catch (ClassNotFoundException e) { + Log.e(TAG, className + ": unable to create instance.", e); + } catch (IllegalAccessException e) { + Log.e(TAG, className + ": unable to create instance.", e); + } catch (InstantiationException e) { + Log.e(TAG, className + ": unable to create instance.", e); + } + return null; + } + + public static ExtendedPhoneDirectoriesManager getExtendedPhoneDirectoriesManager() { + return mExtendedPhoneDirectoriesManager; + } +} diff --git a/src/com/android/contacts/common/format/SpannedTestUtils.java b/src/com/android/contacts/common/format/SpannedTestUtils.java index 8c2a22d0..1fee7d15 100644 --- a/src/com/android/contacts/common/format/SpannedTestUtils.java +++ b/src/com/android/contacts/common/format/SpannedTestUtils.java @@ -18,6 +18,7 @@ package com.android.contacts.common.format; import android.test.suitebuilder.annotation.SmallTest; import android.text.Html; +import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; @@ -80,4 +81,8 @@ public class SpannedTestUtils { Assert.assertFalse(seq instanceof Spanned); Assert.assertEquals(expected, seq); } + + public static int getNextTransition(SpannableString seq, int start) { + return seq.nextSpanTransition(start, seq.length(), ForegroundColorSpan.class); + } } diff --git a/src/com/android/contacts/common/format/PrefixHighlighter.java b/src/com/android/contacts/common/format/TextHighlighter.java index ce44d651..496dcdae 100644 --- a/src/com/android/contacts/common/format/PrefixHighlighter.java +++ b/src/com/android/contacts/common/format/TextHighlighter.java @@ -16,20 +16,29 @@ package com.android.contacts.common.format; +import android.graphics.Typeface; import android.text.SpannableString; +import android.text.style.CharacterStyle; import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; import android.widget.TextView; +import com.google.common.base.Preconditions; + /** * Highlights the text in a text field. */ -public class PrefixHighlighter { - private final int mPrefixHighlightColor; +public class TextHighlighter { + private final String TAG = TextHighlighter.class.getSimpleName(); + private final static boolean DEBUG = false; + + private int mTextStyle; - private ForegroundColorSpan mPrefixColorSpan; + private CharacterStyle mTextStyleSpan; - public PrefixHighlighter(int prefixHighlightColor) { - mPrefixHighlightColor = prefixHighlightColor; + public TextHighlighter(int textStyle) { + mTextStyle = textStyle; + mTextStyleSpan = getStyleSpan(); } /** @@ -39,8 +48,23 @@ public class PrefixHighlighter { * @param text the string to use as the text * @param prefix the prefix to look for */ - public void setText(TextView view, String text, String prefix) { - view.setText(apply(text, prefix)); + 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); } /** @@ -49,7 +73,7 @@ public class PrefixHighlighter { * @param text the text to which to apply the highlight * @param prefix the prefix to look for */ - public CharSequence apply(CharSequence text, String prefix) { + public CharSequence applyPrefixHighlight(CharSequence text, String prefix) { if (prefix == null) { return text; } @@ -64,12 +88,8 @@ public class PrefixHighlighter { int index = FormatUtils.indexOfWordPrefix(text, trimmedPrefix); if (index != -1) { - if (mPrefixColorSpan == null) { - mPrefixColorSpan = new ForegroundColorSpan(mPrefixHighlightColor); - } - - SpannableString result = new SpannableString(text); - result.setSpan(mPrefixColorSpan, index, index + trimmedPrefix.length(), 0 /* flags */); + final SpannableString result = new SpannableString(text); + result.setSpan(mTextStyleSpan, index, index + trimmedPrefix.length(), 0 /* flags */); return result; } else { return text; diff --git a/src/com/android/contacts/common/list/ContactEntry.java b/src/com/android/contacts/common/list/ContactEntry.java new file mode 100644 index 00000000..2683bef8 --- /dev/null +++ b/src/com/android/contacts/common/list/ContactEntry.java @@ -0,0 +1,40 @@ +/* + * 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.contacts.common.list; + +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.provider.ContactsContract.PinnedPositions; + +/** + * Class to hold contact information + */ +public class ContactEntry { + public String name; + public String status; + public String phoneLabel; + public String phoneNumber; + public Uri photoUri; + public Uri lookupKey; + public Drawable presenceIcon; + public long id; + public int pinned = PinnedPositions.UNPINNED; + public boolean isFavorite = false; + public boolean isDefaultNumber = false; + + public static final ContactEntry BLANK_ENTRY = new ContactEntry(); +}
\ No newline at end of file diff --git a/src/com/android/contacts/common/list/ContactEntryListAdapter.java b/src/com/android/contacts/common/list/ContactEntryListAdapter.java index fb9c73a8..bf9be457 100644 --- a/src/com/android/contacts/common/list/ContactEntryListAdapter.java +++ b/src/com/android/contacts/common/list/ContactEntryListAdapter.java @@ -90,8 +90,8 @@ public abstract class ContactEntryListAdapter extends IndexerListAdapter { public ContactEntryListAdapter(Context context) { super(context); - addPartitions(); setDefaultFilterHeaderText(R.string.local_search_label); + addPartitions(); } protected void setDefaultFilterHeaderText(int resourceId) { @@ -134,6 +134,7 @@ public abstract class ContactEntryListAdapter extends IndexerListAdapter { partition.setDirectoryType(getContext().getString(R.string.contactsList)); partition.setPriorityDirectory(true); partition.setPhotoSupported(true); + partition.setLabel(mDefaultFilterHeaderText.toString()); return partition; } @@ -159,7 +160,7 @@ public abstract class ContactEntryListAdapter extends IndexerListAdapter { } } - private int getPartitionByDirectoryId(long id) { + protected int getPartitionByDirectoryId(long id) { int count = getPartitionCount(); for (int i = 0; i < count; i++) { Partition partition = getPartition(i); @@ -172,6 +173,20 @@ public abstract class ContactEntryListAdapter extends IndexerListAdapter { 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 String getContactDisplayName(int position); public abstract void configureLoader(CursorLoader loader, long directoryId); @@ -247,6 +262,11 @@ public abstract class ContactEntryListAdapter extends IndexerListAdapter { return mDirectoryResultLimit; } + public int getDirectoryResultLimit(DirectoryPartition directoryPartition) { + final int limit = directoryPartition.getResultLimit(); + return limit == DirectoryPartition.RESULT_LIMIT_DEFAULT ? mDirectoryResultLimit : limit; + } + public void setDirectoryResultLimit(int limit) { this.mDirectoryResultLimit = limit; } @@ -363,6 +383,11 @@ public abstract class ContactEntryListAdapter extends IndexerListAdapter { if (getPartitionByDirectoryId(id) == -1) { DirectoryPartition partition = new DirectoryPartition(false, true); partition.setDirectoryId(id); + if (isRemoteDirectory(id)) { + partition.setLabel(mContext.getString(R.string.directory_search_label)); + } else { + partition.setLabel(mDefaultFilterHeaderText.toString()); + } partition.setDirectoryType(cursor.getString(directoryTypeColumnIndex)); partition.setDisplayName(cursor.getString(displayNameColumnIndex)); int photoSupport = cursor.getInt(photoSupportColumnIndex); @@ -531,32 +556,23 @@ public abstract class ContactEntryListAdapter extends IndexerListAdapter { long directoryId = directoryPartition.getDirectoryId(); TextView labelTextView = (TextView)view.findViewById(R.id.label); TextView displayNameTextView = (TextView)view.findViewById(R.id.display_name); - if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) { - labelTextView.setText(mDefaultFilterHeaderText); + labelTextView.setText(directoryPartition.getLabel()); + if (!isRemoteDirectory(directoryId)) { displayNameTextView.setText(null); } else { - labelTextView.setText(R.string.directory_search_label); String directoryName = directoryPartition.getDisplayName(); String displayName = !TextUtils.isEmpty(directoryName) ? directoryName : directoryPartition.getDirectoryType(); displayNameTextView.setText(displayName); } + } - TextView countText = (TextView)view.findViewById(R.id.count); - if (directoryPartition.isLoading()) { - countText.setText(R.string.search_results_searching); - } else { - int count = cursor == null ? 0 : cursor.getCount(); - if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE - && count >= getDirectoryResultLimit()) { - countText.setText(mContext.getString( - R.string.foundTooManyContacts, getDirectoryResultLimit())); - } else { - countText.setText(getQuantityText( - count, R.string.listFoundAllContactsZero, R.plurals.searchFoundContacts)); - } - } + // Default implementation simply returns number of rows in the cursor. + // Broken out into its own routine so can be overridden by child classes + // for eg number of unique contacts for a phone list. + protected int getResultCount(Cursor cursor) { + return cursor == null ? 0 : cursor.getCount(); } /** @@ -667,4 +683,9 @@ public abstract class ContactEntryListAdapter extends IndexerListAdapter { public String getContactsCount() { return mContactsCount; } + + public static boolean isRemoteDirectory(long directoryId) { + return directoryId != Directory.DEFAULT + && directoryId != Directory.LOCAL_INVISIBLE; + } } diff --git a/src/com/android/contacts/common/list/ContactEntryListFragment.java b/src/com/android/contacts/common/list/ContactEntryListFragment.java index a8066b81..8df8571d 100644 --- a/src/com/android/contacts/common/list/ContactEntryListFragment.java +++ b/src/com/android/contacts/common/list/ContactEntryListFragment.java @@ -243,6 +243,7 @@ public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter @Override public void onCreate(Bundle savedState) { super.onCreate(savedState); + mAdapter = createListAdapter(); mContactsPrefs = new ContactsPreferences(mContext); restoreSavedState(savedState); } @@ -319,7 +320,7 @@ public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter ContactEntryListAdapter.LOCAL_INVISIBLE_DIRECTORY_ENABLED); return loader; } else { - CursorLoader loader = createCursorLoader(); + CursorLoader loader = createCursorLoader(mContext); long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY) ? args.getLong(DIRECTORY_ID_ARG_KEY) : Directory.DEFAULT; @@ -328,8 +329,8 @@ public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter } } - public CursorLoader createCursorLoader() { - return new CursorLoader(mContext, null, null, null, null, null); + public CursorLoader createCursorLoader(Context context) { + return new CursorLoader(context, null, null, null, null, null); } private void startLoadingDirectoryPartition(int partitionIndex) { @@ -626,6 +627,10 @@ public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter } } + public int getDirectoryLoaderId() { + return DIRECTORY_LOADER_ID; + } + public int getDirectorySearchMode() { return mDirectorySearchMode; } @@ -688,8 +693,6 @@ public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter Bundle savedInstanceState) { onCreateView(inflater, container); - mAdapter = createListAdapter(); - boolean searchMode = isSearchMode(); mAdapter.setSearchMode(searchMode); mAdapter.configureDefaultPartition(false, searchMode); diff --git a/src/com/android/contacts/common/list/ContactListAdapter.java b/src/com/android/contacts/common/list/ContactListAdapter.java index 1be48c42..3ad1801a 100644 --- a/src/com/android/contacts/common/list/ContactListAdapter.java +++ b/src/com/android/contacts/common/list/ContactListAdapter.java @@ -100,6 +100,7 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter { private long mSelectedContactDirectoryId; private String mSelectedContactLookupKey; private long mSelectedContactId; + private ContactListItemView.PhotoPosition mPhotoPosition; public ContactListAdapter(Context context) { super(context); @@ -107,6 +108,14 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter { mUnknownNameText = context.getText(R.string.missing_name); } + public void setPhotoPosition(ContactListItemView.PhotoPosition photoPosition) { + mPhotoPosition = photoPosition; + } + + public ContactListItemView.PhotoPosition getPhotoPosition() { + return mPhotoPosition; + } + public CharSequence getUnknownNameText() { return mUnknownNameText; } @@ -189,6 +198,9 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter { view.setUnknownNameText(mUnknownNameText); view.setQuickContactEnabled(isQuickContactEnabled()); view.setActivatedStateSupported(isSelectionVisible()); + if (mPhotoPosition != null) { + view.setPhotoPosition(mPhotoPosition); + } return view; } diff --git a/src/com/android/contacts/common/list/ContactListItemView.java b/src/com/android/contacts/common/list/ContactListItemView.java index c65a766d..95ac3920 100644 --- a/src/com/android/contacts/common/list/ContactListItemView.java +++ b/src/com/android/contacts/common/list/ContactListItemView.java @@ -34,8 +34,10 @@ import android.text.SpannableString; import android.text.TextUtils; import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; +import android.util.Log; 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; @@ -47,7 +49,7 @@ 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.PrefixHighlighter; +import com.android.contacts.common.format.TextHighlighter; import com.android.contacts.common.util.SearchUtil; import com.google.common.collect.Lists; @@ -110,6 +112,22 @@ public class ContactListItemView extends ViewGroup private Drawable mHorizontalDividerDrawable; private int mHorizontalDividerHeight; + protected static class HighlightSequence { + private final int start; + private final int end; + + HighlightSequence(int start, int end) { + this.start = start; + this.end = end; + } + } + + private ArrayList<HighlightSequence> mNameHighlightSequence; + private ArrayList<HighlightSequence> mNumberHighlightSequence; + + // Highlighting prefix for names. + private String mHighlightedPrefix; + /** * Where to put contact photo. This affects the other Views' layout or look-and-feel. * @@ -155,7 +173,7 @@ public class ContactListItemView extends ViewGroup private ColorStateList mSecondaryTextColor; - private String mHighlightedPrefix; + private int mDefaultPhotoViewSize = 0; /** @@ -210,14 +228,14 @@ public class ContactListItemView extends ViewGroup private Rect mBoundsWithoutHeader = new Rect(); /** A helper used to highlight a prefix in a text field. */ - private PrefixHighlighter mPrefixHighlighter; + private final TextHighlighter mTextHighlighter; private CharSequence mUnknownNameText; public ContactListItemView(Context context) { super(context); mContext = context; - mPrefixHighlighter = new PrefixHighlighter(Color.GREEN); + mTextHighlighter = new TextHighlighter(Typeface.BOLD); } public ContactListItemView(Context context, AttributeSet attrs) { @@ -284,9 +302,8 @@ public class ContactListItemView extends ViewGroup a.getDimensionPixelOffset( R.styleable.ContactListItemView_list_item_padding_bottom, 0)); - final int prefixHighlightColor = a.getColor( - R.styleable.ContactListItemView_list_item_prefix_highlight_color, Color.GREEN); - mPrefixHighlighter = new PrefixHighlighter(prefixHighlightColor); + mTextHighlighter = new TextHighlighter(Typeface.BOLD); + a.recycle(); a = getContext().obtainStyledAttributes(android.R.styleable.Theme); @@ -298,6 +315,9 @@ public class ContactListItemView extends ViewGroup if (mActivatedBackgroundDrawable != null) { mActivatedBackgroundDrawable.setCallback(this); } + + mNameHighlightSequence = new ArrayList<HighlightSequence>(); + mNumberHighlightSequence = new ArrayList<HighlightSequence>(); } public void setUnknownNameText(CharSequence unknownNameText) { @@ -760,7 +780,7 @@ public class ContactListItemView extends ViewGroup mHeaderTextView = new TextView(mContext); mHeaderTextView.setTextColor(mHeaderTextColor); mHeaderTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mHeaderTextSize); - mHeaderTextView.setTypeface(mHeaderTextView.getTypeface(), Typeface.BOLD); + mHeaderTextView.setTextAppearance(mContext, R.style.SectionHeaderStyle); mHeaderTextView.setGravity(Gravity.CENTER_VERTICAL); mHeaderTextView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START); addView(mHeaderTextView); @@ -853,7 +873,7 @@ public class ContactListItemView extends ViewGroup /** * Sets a word prefix that will be highlighted if encountered in fields like - * name and search snippet. + * name and search snippet. This will disable the mask highlighting for names. * <p> * NOTE: must be all upper-case */ @@ -862,6 +882,33 @@ public class ContactListItemView extends ViewGroup } /** + * 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() { @@ -954,7 +1001,7 @@ public class ContactListItemView extends ViewGroup /** * Adds or updates a text view for the data element. */ - public void setData(char[] text, int size, int dataColumnIndex) { + public void setData(char[] text, int size) { if (text == null || size == 0) { if (mDataView != null) { mDataView.setVisibility(View.GONE); @@ -963,14 +1010,36 @@ public class ContactListItemView extends ViewGroup getDataView(); setMarqueeText(mDataView, text, size); mDataView.setVisibility(VISIBLE); - // Check if this is a phone number. This code works also for the legacy phone number - // coming from LegacyPhoneNumberListAdapter.PHONE_NUMBER_COLUMN_INDEX because they are - // the exact same constant value (3) - if (dataColumnIndex == PhoneNumberListAdapter.PhoneQuery.PHONE_NUMBER) { - // 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); + } + } + + /** + * Sets phone number for a list item. This takes care of number highlighting if the highlight + * mask exists. + */ + public void setPhoneNumber(String text) { + if (text == null) { + if (mDataView != null) { + mDataView.setVisibility(View.GONE); + } + } else { + getDataView(); + // 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); } } @@ -1020,7 +1089,7 @@ public class ContactListItemView extends ViewGroup mSnippetView.setVisibility(View.GONE); } } else { - mPrefixHighlighter.setText(getSnippetView(), text, mHighlightedPrefix); + mTextHighlighter.setPrefixText(getSnippetView(), text, mHighlightedPrefix); mSnippetView.setVisibility(VISIBLE); } } @@ -1067,7 +1136,7 @@ public class ContactListItemView extends ViewGroup mCountView.setSingleLine(true); mCountView.setEllipsize(getTextEllipsis()); mCountView.setTextAppearance(mContext, android.R.style.TextAppearance_Medium); - mCountView.setTextColor(R.color.contact_count_text_color); + mCountView.setTextColor(R.color.people_app_theme_color); addView(mCountView); } return mCountView; @@ -1131,12 +1200,7 @@ public class ContactListItemView extends ViewGroup public void showDisplayName(Cursor cursor, int nameColumnIndex, int displayOrder) { CharSequence name = cursor.getString(nameColumnIndex); - if (!TextUtils.isEmpty(name)) { - name = mPrefixHighlighter.apply(name, mHighlightedPrefix); - } else { - name = mUnknownNameText; - } - setMarqueeText(getNameTextView(), name); + 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, @@ -1147,6 +1211,33 @@ public class ContactListItemView extends ViewGroup } } + public void setDisplayName(CharSequence name, boolean highlight) { + if (!TextUtils.isEmpty(name) && highlight) { + clearHighlightSequences(); + addNameHighlightSequence(0, name.length()); + } + setDisplayName(name); + } + + 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); + } + public void hideDisplayName() { if (mNameTextView != null) { removeView(mNameTextView); @@ -1385,11 +1476,16 @@ public class ContactListItemView extends ViewGroup } /** - * Shows data element (e.g. phone number). + * Shows data element. */ public void showData(Cursor cursor, int dataColumnIndex) { cursor.copyStringToBuffer(dataColumnIndex, mDataBuffer); - setData(mDataBuffer.data, mDataBuffer.sizeCopied, dataColumnIndex); + setData(mDataBuffer.data, mDataBuffer.sizeCopied); + } + + public void showPhoneNumber(Cursor cursor, int dataColumnIndex) { + // Highlights the number and aligns text before showing. + setPhoneNumber(cursor.getString(dataColumnIndex)); } public void setActivatedStateSupported(boolean flag) { @@ -1420,4 +1516,29 @@ public class ContactListItemView extends ViewGroup mSelectionBoundsMarginLeft = left; mSelectionBoundsMarginRight = right; } + + /** + * Set drawable resources directly for both the background and the drawable resource + * of the photo view + * + * @param backgroundId Id of background resource + * @param drawableId Id of drawable resource + */ + public void setDrawableResource(int backgroundId, int drawableId) { + final ImageView photo = getPhotoView(); + photo.setScaleType(ImageView.ScaleType.CENTER); + photo.setBackgroundResource(backgroundId); + photo.setImageResource(drawableId); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final float x = event.getX(); + final float y = event.getY(); + if (mBoundsWithoutHeader.contains((int) x, (int) y)) { + return super.onTouchEvent(event); + } else { + return true; + } + } } diff --git a/src/com/android/contacts/common/list/ContactListPinnedHeaderView.java b/src/com/android/contacts/common/list/ContactListPinnedHeaderView.java index bdefd4c8..ea528222 100644 --- a/src/com/android/contacts/common/list/ContactListPinnedHeaderView.java +++ b/src/com/android/contacts/common/list/ContactListPinnedHeaderView.java @@ -84,9 +84,8 @@ public class ContactListPinnedHeaderView extends ViewGroup { mHeaderTextView = new TextView(mContext); mHeaderTextView.setTextColor(mHeaderTextColor); mHeaderTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mHeaderTextSize); - mHeaderTextView.setTypeface(mHeaderTextView.getTypeface(), Typeface.BOLD); mHeaderTextView.setGravity(Gravity.CENTER_VERTICAL); - mHeaderTextView.setAllCaps(true); + mHeaderTextView.setTextAppearance(mContext, R.style.DirectoryHeaderStyle); mHeaderTextView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START); addView(mHeaderTextView); mHeaderDivider = new View(mContext); diff --git a/src/com/android/contacts/common/list/ContactTileAdapter.java b/src/com/android/contacts/common/list/ContactTileAdapter.java index 557ebff6..66467a32 100644 --- a/src/com/android/contacts/common/list/ContactTileAdapter.java +++ b/src/com/android/contacts/common/list/ContactTileAdapter.java @@ -258,6 +258,7 @@ public class ContactTileAdapter extends BaseAdapter { contact.photoUri = (photoUri != null ? Uri.parse(photoUri) : null); contact.lookupKey = ContentUris.withAppendedId( Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), id); + contact.isFavorite = cursor.getInt(mStarredIndex) > 0; // Set phone number and label if (mDisplayType == DisplayType.STREQUENT_PHONE_ONLY) { @@ -652,19 +653,6 @@ public class ContactTileAdapter extends BaseAdapter { } } - /** - * Class to hold contact information - */ - public static class ContactEntry { - public String name; - public String status; - public String phoneLabel; - public String phoneNumber; - public Uri photoUri; - public Uri lookupKey; - public Drawable presenceIcon; - } - protected static class ViewTypes { public static final int COUNT = 4; public static final int STARRED = 0; diff --git a/src/com/android/contacts/common/list/ContactTilePhoneFrequentView.java b/src/com/android/contacts/common/list/ContactTilePhoneFrequentView.java index 2e5f04cb..742ce1c1 100644 --- a/src/com/android/contacts/common/list/ContactTilePhoneFrequentView.java +++ b/src/com/android/contacts/common/list/ContactTilePhoneFrequentView.java @@ -21,7 +21,6 @@ import android.util.AttributeSet; import android.view.View; import com.android.contacts.common.MoreContactUtils; -import com.android.contacts.common.list.ContactTileAdapter.ContactEntry; import com.android.contacts.common.util.ViewUtil; /** diff --git a/src/com/android/contacts/common/list/ContactTileView.java b/src/com/android/contacts/common/list/ContactTileView.java index e30ed49a..c81a81c5 100644 --- a/src/com/android/contacts/common/list/ContactTileView.java +++ b/src/com/android/contacts/common/list/ContactTileView.java @@ -18,6 +18,7 @@ package com.android.contacts.common.list; import android.content.Context; import android.graphics.Rect; import android.net.Uri; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -66,12 +67,7 @@ public abstract class ContactTileView extends FrameLayout { mHorizontalDivider = findViewById(R.id.contact_tile_horizontal_divider); OnClickListener listener = createClickListener(); - - if(mPushState != null) { - mPushState.setOnClickListener(listener); - } else { - setOnClickListener(listener); - } + setOnClickListener(listener); } protected OnClickListener createClickListener() { @@ -92,12 +88,12 @@ public abstract class ContactTileView extends FrameLayout { /** * Populates the data members to be displayed from the - * fields in {@link com.android.contacts.common.list.ContactTileAdapter.ContactEntry} + * fields in {@link com.android.contacts.common.list.ContactEntry} */ - public void loadFromContact(ContactTileAdapter.ContactEntry entry) { + public void loadFromContact(ContactEntry entry) { if (entry != null) { - mName.setText(entry.name); + mName.setText(getNameForView(entry.name)); mLookupUri = entry.lookupKey; if (mStatus != null) { @@ -112,7 +108,12 @@ public abstract class ContactTileView extends FrameLayout { } if (mPhoneLabel != null) { - mPhoneLabel.setText(entry.phoneLabel); + if (TextUtils.isEmpty(entry.phoneLabel)) { + mPhoneLabel.setVisibility(View.GONE); + } else { + mPhoneLabel.setVisibility(View.VISIBLE); + mPhoneLabel.setText(entry.phoneLabel); + } } if (mPhoneNumber != null) { @@ -166,6 +167,14 @@ public abstract class ContactTileView extends FrameLayout { } /** + * Returns the string that should actually be displayed as the contact's name. Subclasses + * can override this to return formatted versions of the name - i.e. first name only. + */ + protected String getNameForView(String name) { + return name; + } + + /** * Implemented by subclasses to estimate the size of the picture. This can return -1 if only * a thumbnail is shown anyway */ diff --git a/src/com/android/contacts/common/list/DefaultContactListAdapter.java b/src/com/android/contacts/common/list/DefaultContactListAdapter.java index 6ad9e8b0..fb974b4d 100644 --- a/src/com/android/contacts/common/list/DefaultContactListAdapter.java +++ b/src/com/android/contacts/common/list/DefaultContactListAdapter.java @@ -78,7 +78,7 @@ public class DefaultContactListAdapter extends ContactListAdapter { String.valueOf(directoryId)); if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE) { builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, - String.valueOf(getDirectoryResultLimit())); + String.valueOf(getDirectoryResultLimit(getDirectoryById(directoryId)))); } builder.appendQueryParameter(SearchSnippetColumns.SNIPPET_ARGS_PARAM_KEY, SNIPPET_ARGS); diff --git a/src/com/android/contacts/common/list/DirectoryPartition.java b/src/com/android/contacts/common/list/DirectoryPartition.java index 022d1e60..ca0dc110 100644 --- a/src/com/android/contacts/common/list/DirectoryPartition.java +++ b/src/com/android/contacts/common/list/DirectoryPartition.java @@ -28,12 +28,19 @@ public final class DirectoryPartition extends CompositeCursorAdapter.Partition { 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); @@ -106,4 +113,67 @@ public final class DirectoryPartition extends CompositeCursorAdapter.Partition { 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/src/com/android/contacts/common/list/OnPhoneNumberPickerActionListener.java b/src/com/android/contacts/common/list/OnPhoneNumberPickerActionListener.java index 9e08fa57..9c6de6bb 100644 --- a/src/com/android/contacts/common/list/OnPhoneNumberPickerActionListener.java +++ b/src/com/android/contacts/common/list/OnPhoneNumberPickerActionListener.java @@ -29,6 +29,8 @@ public interface OnPhoneNumberPickerActionListener { */ void onPickPhoneNumberAction(Uri dataUri); + void onCallNumberDirectly(String phoneNumber); + /** * Returns the selected number as a shortcut intent. */ diff --git a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java index c62b6b1f..ad370353 100644 --- a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java +++ b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java @@ -29,12 +29,15 @@ import android.provider.ContactsContract.ContactCounts; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Directory; -import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.ViewGroup; +import com.android.contacts.common.GeoUtil; import com.android.contacts.common.R; +import com.android.contacts.common.extensions.ExtendedPhoneDirectoriesManager; +import com.android.contacts.common.extensions.ExtensionsFactory; +import com.android.contacts.common.util.Constants; import java.util.ArrayList; import java.util.List; @@ -48,10 +51,19 @@ import java.util.List; * API instead of {@link Phone}. */ public class PhoneNumberListAdapter extends ContactEntryListAdapter { + private static final String TAG = PhoneNumberListAdapter.class.getSimpleName(); - protected static class PhoneQuery { - private static final String[] PROJECTION_PRIMARY = new String[] { + // A list of extended directories to add to the directories from the database + private final List<DirectoryPartition> mExtendedDirectories; + + // Extended directories will have ID's that are higher than any of the id's from the database. + // Thi sis 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; + + public static class PhoneQuery { + public static final String[] PROJECTION_PRIMARY = new String[] { Phone._ID, // 0 Phone.TYPE, // 1 Phone.LABEL, // 2 @@ -60,9 +72,10 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { Phone.LOOKUP_KEY, // 5 Phone.PHOTO_ID, // 6 Phone.DISPLAY_NAME_PRIMARY, // 7 + Phone.PHOTO_THUMBNAIL_URI, // 8 }; - private static final String[] PROJECTION_ALTERNATIVE = new String[] { + public static final String[] PROJECTION_ALTERNATIVE = new String[] { Phone._ID, // 0 Phone.TYPE, // 1 Phone.LABEL, // 2 @@ -71,16 +84,18 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { Phone.LOOKUP_KEY, // 5 Phone.PHOTO_ID, // 6 Phone.DISPLAY_NAME_ALTERNATIVE, // 7 + Phone.PHOTO_THUMBNAIL_URI, // 8 }; - 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 PHONE_CONTACT_ID = 4; - public static final int PHONE_LOOKUP_KEY = 5; - public static final int PHONE_PHOTO_ID = 6; - public static final int PHONE_DISPLAY_NAME = 7; + 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; } private final CharSequence mUnknownNameText; @@ -93,6 +108,15 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { super(context); setDefaultFilterHeaderText(R.string.list_filter_phones); mUnknownNameText = context.getText(android.R.string.unknownName); + + final ExtendedPhoneDirectoriesManager manager + = ExtensionsFactory.getExtendedPhoneDirectoriesManager(); + if (manager != null) { + mExtendedDirectories = manager.getExtendedDirectories(mContext); + } else { + // Empty list to avoid sticky NPE's + mExtendedDirectories = new ArrayList<DirectoryPartition>(); + } } protected CharSequence getUnknownNameText() { @@ -101,52 +125,82 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { @Override public void configureLoader(CursorLoader loader, long directoryId) { - if (directoryId != Directory.DEFAULT) { - Log.w(TAG, "PhoneNumberListAdapter is not ready for non-default directory ID (" - + "directoryId: " + directoryId + ")"); + String query = getQueryString(); + if (query == null) { + query = ""; } - - final Builder builder; - if (isSearchMode()) { - final Uri baseUri = - mUseCallableUri ? Callable.CONTENT_FILTER_URI : Phone.CONTENT_FILTER_URI; - builder = baseUri.buildUpon(); - final String query = getQueryString(); - if (TextUtils.isEmpty(query)) { - builder.appendPath(""); - } else { - builder.appendPath(query); // Builder will encode the 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); } - builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, - String.valueOf(directoryId)); + 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 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(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true"); + final boolean isRemoteDirectoryQuery = isRemoteDirectory(directoryId); + final Builder builder; + if (isSearchMode()) { + final Uri baseUri; + if (isRemoteDirectoryQuery) { + baseUri = Phone.CONTENT_FILTER_URI; + } else if (mUseCallableUri) { + baseUri = Callable.CONTENT_FILTER_URI; + } else { + baseUri = Phone.CONTENT_FILTER_URI; + } + 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 { + final 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(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true"); + } + applyFilter(loader, builder, directoryId, getFilter()); } - applyFilter(loader, builder, directoryId, getFilter()); - } - // Remove duplicates when it is possible. - builder.appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true"); - loader.setUri(builder.build()); + // 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() == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) { - loader.setProjection(PhoneQuery.PROJECTION_PRIMARY); - } else { - loader.setProjection(PhoneQuery.PROJECTION_ALTERNATIVE); - } + // TODO a projection that includes the search snippet + if (getContactNameDisplayOrder() == + ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) { + loader.setProjection(PhoneQuery.PROJECTION_PRIMARY); + } else { + loader.setProjection(PhoneQuery.PROJECTION_ALTERNATIVE); + } - if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) { - loader.setSortOrder(Phone.SORT_KEY_PRIMARY); - } else { - loader.setSortOrder(Phone.SORT_KEY_ALTERNATIVE); + if (getSortOrder() == ContactsContract.Preferences.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}. @@ -188,7 +242,12 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { @Override public String getContactDisplayName(int position) { - return ((Cursor) getItem(position)).getString(PhoneQuery.PHONE_DISPLAY_NAME); + return ((Cursor) getItem(position)).getString(PhoneQuery.DISPLAY_NAME); + } + + public String getPhoneNumber(int position) { + final Cursor item = (Cursor)getItem(position); + return item != null ? item.getString(PhoneQuery.PHONE_NUMBER) : null; } /** @@ -197,14 +256,19 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { * @return Uri for the data. may be null if the cursor is not ready. */ public Uri getDataUri(int position) { - Cursor cursor = ((Cursor)getItem(position)); - if (cursor != null) { - long id = cursor.getLong(PhoneQuery.PHONE_ID); - return ContentUris.withAppendedId(Data.CONTENT_URI, id); - } else { - Log.w(TAG, "Cursor was null in getDataUri() call. Returning null instead."); - return null; + final int partitionIndex = getPartitionForPosition(position); + final Cursor item = (Cursor)getItem(position); + return item != null ? getDataUri(partitionIndex, item) : null; + } + + public Uri getDataUri(int partitionIndex, Cursor cursor) { + final long directoryId = + ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId(); + if (!isRemoteDirectory(directoryId)) { + final long phoneId = cursor.getLong(PhoneQuery.PHONE_ID); + return ContentUris.withAppendedId(Data.CONTENT_URI, phoneId); } + return null; } @Override @@ -217,11 +281,35 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { return view; } + protected void setHighlight(ContactListItemView view, Cursor cursor) { + view.setHighlightedPrefix(isSearchMode() ? getUpperCaseQueryString() : null); + } + + // Override default, which would return number of phone numbers, so we + // instead return number of contacts. + @Override + protected int getResultCount(Cursor cursor) { + if (cursor == null) { + return 0; + } + cursor.moveToPosition(-1); + long curContactId = -1; + int numContacts = 0; + while(cursor.moveToNext()) { + final long contactId = cursor.getLong(PhoneQuery.CONTACT_ID); + if (contactId != curContactId) { + curContactId = contactId; + ++numContacts; + } + } + return numContacts; + } + @Override protected void bindView(View itemView, int partition, Cursor cursor, int position) { ContactListItemView view = (ContactListItemView)itemView; - view.setHighlightedPrefix(isSearchMode() ? getUpperCaseQueryString() : null); + 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. @@ -231,16 +319,16 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { cursor.moveToPosition(position); boolean isFirstEntry = true; boolean showBottomDivider = true; - final long currentContactId = cursor.getLong(PhoneQuery.PHONE_CONTACT_ID); + final long currentContactId = cursor.getLong(PhoneQuery.CONTACT_ID); if (cursor.moveToPrevious() && !cursor.isBeforeFirst()) { - final long previousContactId = cursor.getLong(PhoneQuery.PHONE_CONTACT_ID); + final long previousContactId = cursor.getLong(PhoneQuery.CONTACT_ID); if (currentContactId == previousContactId) { isFirstEntry = false; } } cursor.moveToPosition(position); if (cursor.moveToNext() && !cursor.isAfterLast()) { - final long nextContactId = cursor.getLong(PhoneQuery.PHONE_CONTACT_ID); + final long nextContactId = cursor.getLong(PhoneQuery.CONTACT_ID); if (currentContactId == nextContactId) { // The following entry should be in the same group, which means we don't want a // divider between them. @@ -255,25 +343,28 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { if (isFirstEntry) { bindName(view, cursor); if (isQuickContactEnabled()) { - // No need for photo uri here, because we can not have directory results. If we - // ever do, we need to add photo uri to the query - bindQuickContact(view, partition, cursor, PhoneQuery.PHONE_PHOTO_ID, -1, - PhoneQuery.PHONE_CONTACT_ID, PhoneQuery.PHONE_LOOKUP_KEY); + bindQuickContact(view, partition, cursor, PhoneQuery.PHOTO_ID, + PhoneQuery.PHOTO_URI, PhoneQuery.CONTACT_ID, + PhoneQuery.LOOKUP_KEY); } else { - bindPhoto(view, cursor); + if (getDisplayPhotos()) { + bindPhoto(view, partition, cursor); + } } } else { unbindName(view); view.removePhotoView(true, false); } - bindPhoneNumber(view, cursor); + + final DirectoryPartition directory = (DirectoryPartition) getPartition(partition); + bindPhoneNumber(view, cursor, directory.isDisplayNumber()); view.setDividerVisible(showBottomDivider); } - protected void bindPhoneNumber(ContactListItemView view, Cursor cursor) { + protected void bindPhoneNumber(ContactListItemView view, Cursor cursor, boolean displayNumber) { CharSequence label = null; - if (!cursor.isNull(PhoneQuery.PHONE_TYPE)) { + if (displayNumber && !cursor.isNull(PhoneQuery.PHONE_TYPE)) { final int type = cursor.getInt(PhoneQuery.PHONE_TYPE); final String customLabel = cursor.getString(PhoneQuery.PHONE_LABEL); @@ -281,7 +372,20 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { label = Phone.getTypeLabel(getContext().getResources(), type, customLabel); } view.setLabel(label); - view.showData(cursor, PhoneQuery.PHONE_NUMBER); + final String text; + if (displayNumber) { + text = cursor.getString(PhoneQuery.PHONE_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 = GeoUtil.getGeocodedLocationFor(mContext, phoneNumber); + } + } + view.setPhoneNumber(text); } protected void bindSectionHeaderAndDivider(final ContactListItemView view, int position) { @@ -296,7 +400,7 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { } protected void bindName(final ContactListItemView view, Cursor cursor) { - view.showDisplayName(cursor, PhoneQuery.PHONE_DISPLAY_NAME, getContactNameDisplayOrder()); + view.showDisplayName(cursor, PhoneQuery.DISPLAY_NAME, getContactNameDisplayOrder()); // Note: we don't show phonetic names any more (see issue 5265330) } @@ -304,13 +408,24 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { view.hideDisplayName(); } - protected void bindPhoto(final ContactListItemView view, Cursor cursor) { + protected void bindPhoto(final ContactListItemView view, int partitionIndex, Cursor cursor) { + if (!isPhotoSupported(partitionIndex)) { + view.removePhotoView(); + return; + } + long photoId = 0; - if (!cursor.isNull(PhoneQuery.PHONE_PHOTO_ID)) { - photoId = cursor.getLong(PhoneQuery.PHONE_PHOTO_ID); + if (!cursor.isNull(PhoneQuery.PHOTO_ID)) { + photoId = cursor.getLong(PhoneQuery.PHOTO_ID); } - getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false); + if (photoId != 0) { + getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false); + } else { + final String photoUriString = cursor.getString(PhoneQuery.PHOTO_URI); + final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString); + getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false); + } } public void setPhotoPosition(ContactListItemView.PhotoPosition photoPosition) { @@ -328,4 +443,75 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { public boolean usesCallableUri() { return mUseCallableUri; } + + /** + * 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; + } + final int numExtendedDirectories = mExtendedDirectories.size(); + if (getPartitionCount() == cursor.getCount() + numExtendedDirectories) { + // already added all directories; + return; + } + // + mFirstExtendedDirectoryId = Long.MAX_VALUE; + if (numExtendedDirectories > 0) { + // 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 (!isRemoteDirectory(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 < numExtendedDirectories; i++) { + final long id = mFirstExtendedDirectoryId + i; + final DirectoryPartition directory = mExtendedDirectories.get(i); + if (getPartitionByDirectoryId(id) == -1) { + addPartition(insertIndex, directory); + directory.setDirectoryId(id); + } + } + } + } + + 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(); + } } diff --git a/src/com/android/contacts/common/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/common/list/PhoneNumberPickerFragment.java index 4f99e623..9091fc51 100644 --- a/src/com/android/contacts/common/list/PhoneNumberPickerFragment.java +++ b/src/com/android/contacts/common/list/PhoneNumberPickerFragment.java @@ -15,6 +15,7 @@ */ package com.android.contacts.common.list; +import android.content.CursorLoader; import android.content.Intent; import android.content.Loader; import android.database.Cursor; @@ -79,16 +80,25 @@ public class PhoneNumberPickerFragment extends ContactEntryListFragment<ContactE setQuickContactEnabled(false); setPhotoLoaderEnabled(true); setSectionHeaderDisplayEnabled(true); - setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DATA_SHORTCUT); + setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE); // Show nothing instead of letting caller Activity show something. setHasOptionsMenu(true); } + 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); @@ -178,10 +188,26 @@ public class PhoneNumberPickerFragment extends ContactEntryListFragment<ContactE if (phoneUri != null) { pickPhoneNumber(phoneUri); } else { - Log.w(TAG, "Item at " + position + " was clicked before adapter is ready. Ignoring"); + final String number = getPhoneNumber(position); + if (number != null) { + cacheContactInfo(position); + mListener.onCallNumberDirectly(number); + } else { + Log.w(TAG, "Item at " + position + " was clicked before" + + " adapter is ready. Ignoring"); + } } } + 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 Uri getPhoneUri(int position) { final PhoneNumberListAdapter adapter = (PhoneNumberListAdapter) getAdapter(); return adapter.getDataUri(position); @@ -198,7 +224,7 @@ public class PhoneNumberPickerFragment extends ContactEntryListFragment<ContactE super.onLoadFinished(loader, data); // disable scroll bar if there is no data - setVisibleScrollbarEnabled(data.getCount() > 0); + setVisibleScrollbarEnabled(data != null && data.getCount() > 0); } public void setUseCallableUri(boolean useCallableUri) { diff --git a/src/com/android/contacts/common/list/PinnedHeaderListAdapter.java b/src/com/android/contacts/common/list/PinnedHeaderListAdapter.java index de5ea4fa..72f3f19d 100644 --- a/src/com/android/contacts/common/list/PinnedHeaderListAdapter.java +++ b/src/com/android/contacts/common/list/PinnedHeaderListAdapter.java @@ -58,7 +58,7 @@ public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter } protected boolean isPinnedPartitionHeaderVisible(int partition) { - return mPinnedPartitionHeadersEnabled && hasHeader(partition) + return getPinnedPartitionHeadersEnabled() && hasHeader(partition) && !isPartitionEmpty(partition); } @@ -92,7 +92,7 @@ public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter @Override public void configurePinnedHeaders(PinnedHeaderListView listView) { - if (!mPinnedPartitionHeadersEnabled) { + if (!getPinnedPartitionHeadersEnabled()) { return; } @@ -148,11 +148,8 @@ public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter int height = listView.getPinnedHeaderHeight(i); bottomHeaderHeight += height; - // Animate the header only if the partition is completely invisible below - // the bottom of the view - int firstPositionForPartition = getPositionForPartition(i); - boolean animate = position < firstPositionForPartition; - listView.setHeaderPinnedAtBottom(i, listHeight - bottomHeaderHeight, animate); + + listView.setHeaderPinnedAtBottom(i, listHeight - bottomHeaderHeight, false); maxBottomHeader = i; } } diff --git a/src/com/android/contacts/common/list/PinnedHeaderListView.java b/src/com/android/contacts/common/list/PinnedHeaderListView.java index 9e166df8..034a3dc8 100644 --- a/src/com/android/contacts/common/list/PinnedHeaderListView.java +++ b/src/com/android/contacts/common/list/PinnedHeaderListView.java @@ -76,6 +76,8 @@ public class PinnedHeaderListView extends AutoScrollListView private static final int DEFAULT_ANIMATION_DURATION = 20; + private static final int DEFAULT_SMOOTH_SCROLL_DURATION = 100; + private static final class PinnedHeader { View view; boolean visible; @@ -100,6 +102,9 @@ public class PinnedHeaderListView extends AutoScrollListView 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; @@ -149,6 +154,10 @@ public class PinnedHeaderListView extends AutoScrollListView super.setOnItemSelectedListener(this); } + public void setScrollToSectionOnHeaderTouch(boolean value) { + mScrollToSectionOnHeaderTouch = value; + } + @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { @@ -398,12 +407,19 @@ public class PinnedHeaderListView extends AutoScrollListView @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(); for (int i = mSize; --i >= 0;) { PinnedHeader header = mHeaders[i]; if (header.visible && header.y <= y && header.y + header.height > y) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mHeaderTouched = true; + if (mScrollToSectionOnHeaderTouch && + ev.getAction() == MotionEvent.ACTION_DOWN) { return smoothScrollToPartition(i); } else { return true; @@ -412,9 +428,20 @@ public class PinnedHeaderListView extends AutoScrollListView } } - return super.onInterceptTouchEvent(ev); + 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) { final int position = mAdapter.getScrollPositionForHeader(partition); if (position == -1) { @@ -428,8 +455,8 @@ public class PinnedHeaderListView extends AutoScrollListView offset += header.height; } } - - smoothScrollToPositionFromTop(position + getHeaderViewsCount(), offset); + smoothScrollToPositionFromTop(position + getHeaderViewsCount(), offset, + DEFAULT_SMOOTH_SCROLL_DURATION); return true; } diff --git a/src/com/android/contacts/common/model/AccountTypeManager.java b/src/com/android/contacts/common/model/AccountTypeManager.java index a8eb3778..b5a9a165 100644 --- a/src/com/android/contacts/common/model/AccountTypeManager.java +++ b/src/com/android/contacts/common/model/AccountTypeManager.java @@ -23,7 +23,6 @@ import android.accounts.OnAccountsUpdateListener; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; -import android.content.IContentService; import android.content.Intent; import android.content.IntentFilter; import android.content.SyncAdapterType; @@ -395,102 +394,93 @@ class AccountTypeManagerImpl extends AccountTypeManager final Set<String> extensionPackages = Sets.newHashSet(); final AccountManager am = mAccountManager; - final IContentService cs = ContentResolver.getContentService(); - try { - final SyncAdapterType[] syncs = cs.getSyncAdapterTypes(); - final AuthenticatorDescription[] auths = am.getAuthenticatorTypes(); + final SyncAdapterType[] syncs = ContentResolver.getSyncAdapterTypes(); + final AuthenticatorDescription[] auths = am.getAuthenticatorTypes(); - // First process sync adapters to find any that provide contact data. - for (SyncAdapterType sync : syncs) { - if (!ContactsContract.AUTHORITY.equals(sync.authority)) { - // Skip sync adapters that don't provide contact data. - continue; - } + // First process sync adapters to find any that provide contact data. + for (SyncAdapterType sync : syncs) { + if (!ContactsContract.AUTHORITY.equals(sync.authority)) { + // Skip sync adapters that don't provide contact data. + continue; + } - // Look for the formatting details provided by each sync - // adapter, using the authenticator to find general resources. - final String type = sync.accountType; - final AuthenticatorDescription auth = findAuthenticator(auths, type); - if (auth == null) { - Log.w(TAG, "No authenticator found for type=" + type + ", ignoring it."); - continue; - } + // Look for the formatting details provided by each sync + // adapter, using the authenticator to find general resources. + final String type = sync.accountType; + final AuthenticatorDescription auth = findAuthenticator(auths, type); + if (auth == null) { + Log.w(TAG, "No authenticator found for type=" + type + ", ignoring it."); + continue; + } - AccountType accountType; - if (GoogleAccountType.ACCOUNT_TYPE.equals(type)) { - accountType = new GoogleAccountType(mContext, auth.packageName); - } else if (ExchangeAccountType.isExchangeType(type)) { - accountType = new ExchangeAccountType(mContext, auth.packageName, type); + AccountType accountType; + if (GoogleAccountType.ACCOUNT_TYPE.equals(type)) { + accountType = new GoogleAccountType(mContext, auth.packageName); + } else if (ExchangeAccountType.isExchangeType(type)) { + accountType = new ExchangeAccountType(mContext, auth.packageName, type); + } else { + // TODO: use syncadapter package instead, since it provides resources + Log.d(TAG, "Registering external account type=" + type + + ", packageName=" + auth.packageName); + accountType = new ExternalAccountType(mContext, auth.packageName, false); + } + if (!accountType.isInitialized()) { + if (accountType.isEmbedded()) { + throw new IllegalStateException("Problem initializing embedded type " + + accountType.getClass().getCanonicalName()); } else { - // TODO: use syncadapter package instead, since it provides resources - Log.d(TAG, "Registering external account type=" + type - + ", packageName=" + auth.packageName); - accountType = new ExternalAccountType(mContext, auth.packageName, false); - } - if (!accountType.isInitialized()) { - if (accountType.isEmbedded()) { - throw new IllegalStateException("Problem initializing embedded type " - + accountType.getClass().getCanonicalName()); - } else { - // Skip external account types that couldn't be initialized. - continue; - } + // Skip external account types that couldn't be initialized. + continue; } + } - accountType.accountType = auth.type; - accountType.titleRes = auth.labelId; - accountType.iconRes = auth.iconId; - - addAccountType(accountType, accountTypesByTypeAndDataSet, accountTypesByType); + accountType.accountType = auth.type; + accountType.titleRes = auth.labelId; + accountType.iconRes = auth.iconId; - // Check to see if the account type knows of any other non-sync-adapter packages - // that may provide other data sets of contact data. - extensionPackages.addAll(accountType.getExtensionPackageNames()); - } + addAccountType(accountType, accountTypesByTypeAndDataSet, accountTypesByType); - // If any extension packages were specified, process them as well. - if (!extensionPackages.isEmpty()) { - Log.d(TAG, "Registering " + extensionPackages.size() + " extension packages"); - for (String extensionPackage : extensionPackages) { - ExternalAccountType accountType = - new ExternalAccountType(mContext, extensionPackage, true); - if (!accountType.isInitialized()) { - // Skip external account types that couldn't be initialized. - continue; - } - if (!accountType.hasContactsMetadata()) { - Log.w(TAG, "Skipping extension package " + extensionPackage + " because" - + " it doesn't have the CONTACTS_STRUCTURE metadata"); - continue; - } - if (TextUtils.isEmpty(accountType.accountType)) { - Log.w(TAG, "Skipping extension package " + extensionPackage + " because" - + " the CONTACTS_STRUCTURE metadata doesn't have the accountType" - + " attribute"); - continue; - } - Log.d(TAG, "Registering extension package account type=" - + accountType.accountType + ", dataSet=" + accountType.dataSet - + ", packageName=" + extensionPackage); + // Check to see if the account type knows of any other non-sync-adapter packages + // that may provide other data sets of contact data. + extensionPackages.addAll(accountType.getExtensionPackageNames()); + } - addAccountType(accountType, accountTypesByTypeAndDataSet, accountTypesByType); + // If any extension packages were specified, process them as well. + if (!extensionPackages.isEmpty()) { + Log.d(TAG, "Registering " + extensionPackages.size() + " extension packages"); + for (String extensionPackage : extensionPackages) { + ExternalAccountType accountType = + new ExternalAccountType(mContext, extensionPackage, true); + if (!accountType.isInitialized()) { + // Skip external account types that couldn't be initialized. + continue; } + if (!accountType.hasContactsMetadata()) { + Log.w(TAG, "Skipping extension package " + extensionPackage + " because" + + " it doesn't have the CONTACTS_STRUCTURE metadata"); + continue; + } + if (TextUtils.isEmpty(accountType.accountType)) { + Log.w(TAG, "Skipping extension package " + extensionPackage + " because" + + " the CONTACTS_STRUCTURE metadata doesn't have the accountType" + + " attribute"); + continue; + } + Log.d(TAG, "Registering extension package account type=" + + accountType.accountType + ", dataSet=" + accountType.dataSet + + ", packageName=" + extensionPackage); + + addAccountType(accountType, accountTypesByTypeAndDataSet, accountTypesByType); } - } catch (RemoteException e) { - Log.w(TAG, "Problem loading accounts: " + e.toString()); } timings.addSplit("Loaded account types"); // Map in accounts to associate the account names with each account type entry. Account[] accounts = mAccountManager.getAccounts(); for (Account account : accounts) { - boolean syncable = false; - try { - syncable = cs.getIsSyncable(account, ContactsContract.AUTHORITY) > 0; - } catch (RemoteException e) { - Log.e(TAG, "Cannot obtain sync flag for account: " + account, e); - } + boolean syncable = + ContentResolver.getIsSyncable(account, ContactsContract.AUTHORITY) > 0; if (syncable) { List<AccountType> accountTypes = accountTypesByType.get(account.type); diff --git a/src/com/android/contacts/common/util/Constants.java b/src/com/android/contacts/common/util/Constants.java index 46ddbae0..c0d67551 100644 --- a/src/com/android/contacts/common/util/Constants.java +++ b/src/com/android/contacts/common/util/Constants.java @@ -24,4 +24,6 @@ public class Constants { */ public static final String PERFORMANCE_TAG = "ContactsPerf"; + // Used for lookup URI that contains an encoded JSON string. + public static final String LOOKUP_URI_ENCODED = "encoded"; } diff --git a/src/com/android/contacts/common/util/UriUtils.java b/src/com/android/contacts/common/util/UriUtils.java index 6045f546..352da489 100644 --- a/src/com/android/contacts/common/util/UriUtils.java +++ b/src/com/android/contacts/common/util/UriUtils.java @@ -48,4 +48,8 @@ public class UriUtils { public static String uriToString(Uri uri) { return uri == null ? null : uri.toString(); } + + public static boolean isEncodedContactUri(Uri uri) { + return uri.getLastPathSegment().equals(Constants.LOOKUP_URI_ENCODED); + } } |