summaryrefslogtreecommitdiffstats
path: root/src/com/android/contacts/common
diff options
context:
space:
mode:
authorRicardo Cerqueira <cyanogenmod@cerqueira.org>2013-11-05 20:04:57 +0000
committerRicardo Cerqueira <cyanogenmod@cerqueira.org>2013-11-05 20:04:57 +0000
commita42f690f2014b3588d67677c5f30859790600eb7 (patch)
treef5af06adc40a5a5c40377d060a9b0533b8e6cb09 /src/com/android/contacts/common
parentd70b3939dcbaa1ca04a120b9c8aed00971824ac9 (diff)
parent1d562555a51937778a16d0f7f19a61bce48aaf1c (diff)
downloadandroid_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')
-rw-r--r--src/com/android/contacts/common/Collapser.java10
-rw-r--r--src/com/android/contacts/common/ContactPhotoManager.java9
-rw-r--r--src/com/android/contacts/common/ContactTileLoaderFactory.java11
-rw-r--r--src/com/android/contacts/common/GeoUtil.java36
-rw-r--r--src/com/android/contacts/common/MoreContactUtils.java1
-rw-r--r--src/com/android/contacts/common/extensions/ExtendedPhoneDirectoriesManager.java26
-rw-r--r--src/com/android/contacts/common/extensions/ExtensionsFactory.java89
-rw-r--r--src/com/android/contacts/common/format/SpannedTestUtils.java5
-rw-r--r--src/com/android/contacts/common/format/TextHighlighter.java (renamed from src/com/android/contacts/common/format/PrefixHighlighter.java)48
-rw-r--r--src/com/android/contacts/common/list/ContactEntry.java40
-rw-r--r--src/com/android/contacts/common/list/ContactEntryListAdapter.java59
-rw-r--r--src/com/android/contacts/common/list/ContactEntryListFragment.java13
-rw-r--r--src/com/android/contacts/common/list/ContactListAdapter.java12
-rw-r--r--src/com/android/contacts/common/list/ContactListItemView.java175
-rw-r--r--src/com/android/contacts/common/list/ContactListPinnedHeaderView.java3
-rw-r--r--src/com/android/contacts/common/list/ContactTileAdapter.java14
-rw-r--r--src/com/android/contacts/common/list/ContactTilePhoneFrequentView.java1
-rw-r--r--src/com/android/contacts/common/list/ContactTileView.java29
-rw-r--r--src/com/android/contacts/common/list/DefaultContactListAdapter.java2
-rw-r--r--src/com/android/contacts/common/list/DirectoryPartition.java70
-rw-r--r--src/com/android/contacts/common/list/OnPhoneNumberPickerActionListener.java2
-rw-r--r--src/com/android/contacts/common/list/PhoneNumberListAdapter.java334
-rw-r--r--src/com/android/contacts/common/list/PhoneNumberPickerFragment.java32
-rw-r--r--src/com/android/contacts/common/list/PinnedHeaderListAdapter.java11
-rw-r--r--src/com/android/contacts/common/list/PinnedHeaderListView.java35
-rw-r--r--src/com/android/contacts/common/model/AccountTypeManager.java148
-rw-r--r--src/com/android/contacts/common/util/Constants.java2
-rw-r--r--src/com/android/contacts/common/util/UriUtils.java4
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);
+ }
}