diff options
author | Ben Gilad <gilad@google.com> | 2013-08-21 09:46:00 -0700 |
---|---|---|
committer | Alon Albert <aalbert@google.com> | 2013-08-27 13:32:02 -0700 |
commit | ad582fa758c6a276c26c5cc52079613a653c81bf (patch) | |
tree | 75a42cbaaff649a4bc7dabe782e32dbf8164ebed /src | |
parent | e31b07390729cced594404410efb4448f94a95cb (diff) | |
download | android_packages_apps_ContactsCommon-ad582fa758c6a276c26c5cc52079613a653c81bf.tar.gz android_packages_apps_ContactsCommon-ad582fa758c6a276c26c5cc52079613a653c81bf.tar.bz2 android_packages_apps_ContactsCommon-ad582fa758c6a276c26c5cc52079613a653c81bf.zip |
Add Support For Extended Directories
Change-Id: I5097f9d45ce39aad93ede3a01f43e1c58e36c9f1
Diffstat (limited to 'src')
6 files changed, 290 insertions, 49 deletions
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/list/ContactEntryListAdapter.java b/src/com/android/contacts/common/list/ContactEntryListAdapter.java index fb9c73a8..c9e74e43 100644 --- a/src/com/android/contacts/common/list/ContactEntryListAdapter.java +++ b/src/com/android/contacts/common/list/ContactEntryListAdapter.java @@ -159,7 +159,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 +172,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 +261,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; } @@ -548,10 +567,10 @@ public abstract class ContactEntryListAdapter extends IndexerListAdapter { countText.setText(R.string.search_results_searching); } else { int count = cursor == null ? 0 : cursor.getCount(); + final int limit = getDirectoryResultLimit(directoryPartition); if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE - && count >= getDirectoryResultLimit()) { - countText.setText(mContext.getString( - R.string.foundTooManyContacts, getDirectoryResultLimit())); + && count >= limit) { + countText.setText(mContext.getString(R.string.foundTooManyContacts, limit)); } else { countText.setText(getQuantityText( count, R.string.listFoundAllContactsZero, R.plurals.searchFoundContacts)); 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..9a8d4cc9 100644 --- a/src/com/android/contacts/common/list/DirectoryPartition.java +++ b/src/com/android/contacts/common/list/DirectoryPartition.java @@ -28,12 +28,15 @@ 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 mDirectoryType; private String mDisplayName; private int mStatus; private boolean mPriorityDirectory; private boolean mPhotoSupported; + private int mResultLimit = RESULT_LIMIT_DEFAULT; public DirectoryPartition(boolean showIfEmpty, boolean hasHeader) { super(showIfEmpty, hasHeader); @@ -106,4 +109,17 @@ 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; + } } diff --git a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java index e58e257e..43797cce 100644 --- a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java +++ b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java @@ -29,13 +29,13 @@ 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.R; -import com.android.contacts.common.CallUtil; +import com.android.contacts.common.extensions.ExtendedPhoneDirectoriesManager; +import com.android.contacts.common.extensions.ExtensionsFactory; import java.util.ArrayList; import java.util.List; @@ -49,8 +49,17 @@ import java.util.List; * API instead of {@link Phone}. */ public class PhoneNumberListAdapter extends ContactEntryListAdapter { + private static final String TAG = PhoneNumberListAdapter.class.getSimpleName(); + // 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 @@ -97,6 +106,14 @@ 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 { + mExtendedDirectories = null; + } } protected CharSequence getUnknownNameText() { @@ -110,56 +127,75 @@ public class PhoneNumberListAdapter extends ContactEntryListAdapter { @Override public void configureLoader(CursorLoader loader, long directoryId) { - 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; + String query = getQueryString(); + if (query == null) { + query = ""; + } + if (isExtendedDirectory(directoryId)) { + final DirectoryPartition directory = getExtendedDirectoryFromId(directoryId); + final Builder builder = Uri.parse(directory.getDirectoryType()).buildUpon(); + builder.appendPath(query); + builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, + String.valueOf(getDirectoryResultLimit(directory))); + loader.setUri(builder.build()); + loader.setProjection(PhoneQuery.PROJECTION_PRIMARY); + } else { + final boolean isRemoteDirectoryQuery = 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 { - baseUri = Phone.CONTENT_FILTER_URI; + 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()); } - builder = baseUri.buildUpon(); - final String query = getQueryString(); - if (TextUtils.isEmpty(query)) { - builder.appendPath(""); + + // 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 { - 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())); + loader.setProjection(PhoneQuery.PROJECTION_ALTERNATIVE); } - } 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"); + + if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) { + loader.setSortOrder(Phone.SORT_KEY_PRIMARY); + } else { + loader.setSortOrder(Phone.SORT_KEY_ALTERNATIVE); } - applyFilter(loader, builder, directoryId, getFilter()); } + } - // 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); - } + private boolean isExtendedDirectory(long directoryId) { + return directoryId >= mFirstExtendedDirectoryId; + } - if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) { - loader.setSortOrder(Phone.SORT_KEY_PRIMARY); - } else { - loader.setSortOrder(Phone.SORT_KEY_ALTERNATIVE); - } + private DirectoryPartition getExtendedDirectoryFromId(long directoryId) { + final int directoryIndex = (int) (directoryId - mFirstExtendedDirectoryId); + return mExtendedDirectories.get(directoryIndex); } /** @@ -368,4 +404,59 @@ 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); + } + } + } + } } |