Home | History | Annotate | Download | only in list
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.contacts.list;
     17 
     18 import com.android.contacts.ContactPhotoManager;
     19 import com.android.contacts.R;
     20 import com.android.contacts.widget.IndexerListAdapter;
     21 
     22 import android.content.Context;
     23 import android.content.CursorLoader;
     24 import android.database.Cursor;
     25 import android.net.Uri;
     26 import android.os.Bundle;
     27 import android.provider.ContactsContract;
     28 import android.provider.ContactsContract.ContactCounts;
     29 import android.provider.ContactsContract.Contacts;
     30 import android.provider.ContactsContract.Directory;
     31 import android.text.TextUtils;
     32 import android.util.Log;
     33 import android.view.LayoutInflater;
     34 import android.view.View;
     35 import android.view.ViewGroup;
     36 import android.widget.QuickContactBadge;
     37 import android.widget.SectionIndexer;
     38 import android.widget.TextView;
     39 
     40 import java.util.HashSet;
     41 
     42 /**
     43  * Common base class for various contact-related lists, e.g. contact list, phone number list
     44  * etc.
     45  */
     46 public abstract class ContactEntryListAdapter extends IndexerListAdapter {
     47 
     48     private static final String TAG = "ContactEntryListAdapter";
     49 
     50     /**
     51      * Indicates whether the {@link Directory#LOCAL_INVISIBLE} directory should
     52      * be included in the search.
     53      */
     54     private static final boolean LOCAL_INVISIBLE_DIRECTORY_ENABLED = false;
     55 
     56     private int mDisplayOrder;
     57     private int mSortOrder;
     58 
     59     private boolean mDisplayPhotos;
     60     private boolean mQuickContactEnabled;
     61 
     62     /**
     63      * indicates if contact queries include profile
     64      */
     65     private boolean mIncludeProfile;
     66 
     67     /**
     68      * indicates if query results includes a profile
     69      */
     70     private boolean mProfileExists;
     71 
     72     private ContactPhotoManager mPhotoLoader;
     73 
     74     private String mQueryString;
     75     private char[] mUpperCaseQueryString;
     76     private boolean mSearchMode;
     77     private int mDirectorySearchMode;
     78     private int mDirectoryResultLimit = Integer.MAX_VALUE;
     79 
     80     private boolean mLoading = true;
     81     private boolean mEmptyListEnabled = true;
     82 
     83     private boolean mSelectionVisible;
     84 
     85     private ContactListFilter mFilter;
     86     private String mContactsCount = "";
     87     private boolean mDarkTheme = false;
     88 
     89     /** Resource used to provide header-text for default filter. */
     90     private CharSequence mDefaultFilterHeaderText;
     91 
     92     public ContactEntryListAdapter(Context context) {
     93         super(context);
     94         addPartitions();
     95         setDefaultFilterHeaderText(R.string.local_search_label);
     96     }
     97 
     98     protected void setDefaultFilterHeaderText(int resourceId) {
     99         mDefaultFilterHeaderText = getContext().getResources().getText(resourceId);
    100     }
    101 
    102     @Override
    103     protected View createPinnedSectionHeaderView(Context context, ViewGroup parent) {
    104         return new ContactListPinnedHeaderView(context, null);
    105     }
    106 
    107     @Override
    108     protected void setPinnedSectionTitle(View pinnedHeaderView, String title) {
    109         ((ContactListPinnedHeaderView)pinnedHeaderView).setSectionHeader(title);
    110     }
    111 
    112     @Override
    113     protected void setPinnedHeaderContactsCount(View header) {
    114         // Update the header with the contacts count only if a profile header exists
    115         // otherwise, the contacts count are shown in the empty profile header view
    116         if (mProfileExists) {
    117             ((ContactListPinnedHeaderView)header).setCountView(mContactsCount);
    118         } else {
    119             clearPinnedHeaderContactsCount(header);
    120         }
    121     }
    122 
    123     @Override
    124     protected void clearPinnedHeaderContactsCount(View header) {
    125         ((ContactListPinnedHeaderView)header).setCountView(null);
    126     }
    127 
    128     protected void addPartitions() {
    129         addPartition(createDefaultDirectoryPartition());
    130     }
    131 
    132     protected DirectoryPartition createDefaultDirectoryPartition() {
    133         DirectoryPartition partition = new DirectoryPartition(true, true);
    134         partition.setDirectoryId(Directory.DEFAULT);
    135         partition.setDirectoryType(getContext().getString(R.string.contactsList));
    136         partition.setPriorityDirectory(true);
    137         partition.setPhotoSupported(true);
    138         return partition;
    139     }
    140 
    141     /**
    142      * Remove all directories after the default directory. This is typically used when contacts
    143      * list screens are asked to exit the search mode and thus need to remove all remote directory
    144      * results for the search.
    145      *
    146      * This code assumes that the default directory and directories before that should not be
    147      * deleted (e.g. Join screen has "suggested contacts" directory before the default director,
    148      * and we should not remove the directory).
    149      */
    150     /* package */ void removeDirectoriesAfterDefault() {
    151         final int partitionCount = getPartitionCount();
    152         for (int i = partitionCount - 1; i >= 0; i--) {
    153             final Partition partition = getPartition(i);
    154             if ((partition instanceof DirectoryPartition)
    155                     && ((DirectoryPartition) partition).getDirectoryId() == Directory.DEFAULT) {
    156                 break;
    157             } else {
    158                 removePartition(i);
    159             }
    160         }
    161     }
    162 
    163     private int getPartitionByDirectoryId(long id) {
    164         int count = getPartitionCount();
    165         for (int i = 0; i < count; i++) {
    166             Partition partition = getPartition(i);
    167             if (partition instanceof DirectoryPartition) {
    168                 if (((DirectoryPartition)partition).getDirectoryId() == id) {
    169                     return i;
    170                 }
    171             }
    172         }
    173         return -1;
    174     }
    175 
    176     public abstract String getContactDisplayName(int position);
    177     public abstract void configureLoader(CursorLoader loader, long directoryId);
    178 
    179     /**
    180      * Marks all partitions as "loading"
    181      */
    182     public void onDataReload() {
    183         boolean notify = false;
    184         int count = getPartitionCount();
    185         for (int i = 0; i < count; i++) {
    186             Partition partition = getPartition(i);
    187             if (partition instanceof DirectoryPartition) {
    188                 DirectoryPartition directoryPartition = (DirectoryPartition)partition;
    189                 if (!directoryPartition.isLoading()) {
    190                     notify = true;
    191                 }
    192                 directoryPartition.setStatus(DirectoryPartition.STATUS_NOT_LOADED);
    193             }
    194         }
    195         if (notify) {
    196             notifyDataSetChanged();
    197         }
    198     }
    199 
    200     @Override
    201     public void clearPartitions() {
    202         int count = getPartitionCount();
    203         for (int i = 0; i < count; i++) {
    204             Partition partition = getPartition(i);
    205             if (partition instanceof DirectoryPartition) {
    206                 DirectoryPartition directoryPartition = (DirectoryPartition)partition;
    207                 directoryPartition.setStatus(DirectoryPartition.STATUS_NOT_LOADED);
    208             }
    209         }
    210         super.clearPartitions();
    211     }
    212 
    213     public boolean isSearchMode() {
    214         return mSearchMode;
    215     }
    216 
    217     public void setSearchMode(boolean flag) {
    218         mSearchMode = flag;
    219     }
    220 
    221     public String getQueryString() {
    222         return mQueryString;
    223     }
    224 
    225     public void setQueryString(String queryString) {
    226         mQueryString = queryString;
    227         if (TextUtils.isEmpty(queryString)) {
    228             mUpperCaseQueryString = null;
    229         } else {
    230             mUpperCaseQueryString = queryString.toUpperCase().toCharArray();
    231         }
    232     }
    233 
    234     public char[] getUpperCaseQueryString() {
    235         return mUpperCaseQueryString;
    236     }
    237 
    238     public int getDirectorySearchMode() {
    239         return mDirectorySearchMode;
    240     }
    241 
    242     public void setDirectorySearchMode(int mode) {
    243         mDirectorySearchMode = mode;
    244     }
    245 
    246     public int getDirectoryResultLimit() {
    247         return mDirectoryResultLimit;
    248     }
    249 
    250     public void setDirectoryResultLimit(int limit) {
    251         this.mDirectoryResultLimit = limit;
    252     }
    253 
    254     public int getContactNameDisplayOrder() {
    255         return mDisplayOrder;
    256     }
    257 
    258     public void setContactNameDisplayOrder(int displayOrder) {
    259         mDisplayOrder = displayOrder;
    260     }
    261 
    262     public int getSortOrder() {
    263         return mSortOrder;
    264     }
    265 
    266     public void setSortOrder(int sortOrder) {
    267         mSortOrder = sortOrder;
    268     }
    269 
    270     public void setPhotoLoader(ContactPhotoManager photoLoader) {
    271         mPhotoLoader = photoLoader;
    272     }
    273 
    274     protected ContactPhotoManager getPhotoLoader() {
    275         return mPhotoLoader;
    276     }
    277 
    278     public boolean getDisplayPhotos() {
    279         return mDisplayPhotos;
    280     }
    281 
    282     public void setDisplayPhotos(boolean displayPhotos) {
    283         mDisplayPhotos = displayPhotos;
    284     }
    285 
    286     public boolean isEmptyListEnabled() {
    287         return mEmptyListEnabled;
    288     }
    289 
    290     public void setEmptyListEnabled(boolean flag) {
    291         mEmptyListEnabled = flag;
    292     }
    293 
    294     public boolean isSelectionVisible() {
    295         return mSelectionVisible;
    296     }
    297 
    298     public void setSelectionVisible(boolean flag) {
    299         this.mSelectionVisible = flag;
    300     }
    301 
    302     public boolean isQuickContactEnabled() {
    303         return mQuickContactEnabled;
    304     }
    305 
    306     public void setQuickContactEnabled(boolean quickContactEnabled) {
    307         mQuickContactEnabled = quickContactEnabled;
    308     }
    309 
    310     public boolean shouldIncludeProfile() {
    311         return mIncludeProfile;
    312     }
    313 
    314     public void setIncludeProfile(boolean includeProfile) {
    315         mIncludeProfile = includeProfile;
    316     }
    317 
    318     public void setProfileExists(boolean exists) {
    319         mProfileExists = exists;
    320         // Stick the "ME" header for the profile
    321         if (exists) {
    322             SectionIndexer indexer = getIndexer();
    323             if (indexer != null) {
    324                 ((ContactsSectionIndexer) indexer).setProfileHeader(
    325                         getContext().getString(R.string.user_profile_contacts_list_header));
    326             }
    327         }
    328     }
    329 
    330     public boolean hasProfile() {
    331         return mProfileExists;
    332     }
    333 
    334     public void setDarkTheme(boolean value) {
    335         mDarkTheme = value;
    336     }
    337 
    338     public void configureDirectoryLoader(DirectoryListLoader loader) {
    339         loader.setDirectorySearchMode(mDirectorySearchMode);
    340         loader.setLocalInvisibleDirectoryEnabled(LOCAL_INVISIBLE_DIRECTORY_ENABLED);
    341     }
    342 
    343     /**
    344      * Updates partitions according to the directory meta-data contained in the supplied
    345      * cursor.
    346      */
    347     public void changeDirectories(Cursor cursor) {
    348         if (cursor.getCount() == 0) {
    349             // Directory table must have at least local directory, without which this adapter will
    350             // enter very weird state.
    351             Log.e(TAG, "Directory search loader returned an empty cursor, which implies we have " +
    352                     "no directory entries.", new RuntimeException());
    353             return;
    354         }
    355         HashSet<Long> directoryIds = new HashSet<Long>();
    356 
    357         int idColumnIndex = cursor.getColumnIndex(Directory._ID);
    358         int directoryTypeColumnIndex = cursor.getColumnIndex(DirectoryListLoader.DIRECTORY_TYPE);
    359         int displayNameColumnIndex = cursor.getColumnIndex(Directory.DISPLAY_NAME);
    360         int photoSupportColumnIndex = cursor.getColumnIndex(Directory.PHOTO_SUPPORT);
    361 
    362         // TODO preserve the order of partition to match those of the cursor
    363         // Phase I: add new directories
    364         cursor.moveToPosition(-1);
    365         while (cursor.moveToNext()) {
    366             long id = cursor.getLong(idColumnIndex);
    367             directoryIds.add(id);
    368             if (getPartitionByDirectoryId(id) == -1) {
    369                 DirectoryPartition partition = new DirectoryPartition(false, true);
    370                 partition.setDirectoryId(id);
    371                 partition.setDirectoryType(cursor.getString(directoryTypeColumnIndex));
    372                 partition.setDisplayName(cursor.getString(displayNameColumnIndex));
    373                 int photoSupport = cursor.getInt(photoSupportColumnIndex);
    374                 partition.setPhotoSupported(photoSupport == Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY
    375                         || photoSupport == Directory.PHOTO_SUPPORT_FULL);
    376                 addPartition(partition);
    377             }
    378         }
    379 
    380         // Phase II: remove deleted directories
    381         int count = getPartitionCount();
    382         for (int i = count; --i >= 0; ) {
    383             Partition partition = getPartition(i);
    384             if (partition instanceof DirectoryPartition) {
    385                 long id = ((DirectoryPartition)partition).getDirectoryId();
    386                 if (!directoryIds.contains(id)) {
    387                     removePartition(i);
    388                 }
    389             }
    390         }
    391 
    392         invalidate();
    393         notifyDataSetChanged();
    394     }
    395 
    396     @Override
    397     public void changeCursor(int partitionIndex, Cursor cursor) {
    398         if (partitionIndex >= getPartitionCount()) {
    399             // There is no partition for this data
    400             return;
    401         }
    402 
    403         Partition partition = getPartition(partitionIndex);
    404         if (partition instanceof DirectoryPartition) {
    405             ((DirectoryPartition)partition).setStatus(DirectoryPartition.STATUS_LOADED);
    406         }
    407 
    408         if (mDisplayPhotos && mPhotoLoader != null && isPhotoSupported(partitionIndex)) {
    409             mPhotoLoader.refreshCache();
    410         }
    411 
    412         super.changeCursor(partitionIndex, cursor);
    413 
    414         if (isSectionHeaderDisplayEnabled() && partitionIndex == getIndexedPartition()) {
    415             updateIndexer(cursor);
    416         }
    417     }
    418 
    419     public void changeCursor(Cursor cursor) {
    420         changeCursor(0, cursor);
    421     }
    422 
    423     /**
    424      * Updates the indexer, which is used to produce section headers.
    425      */
    426     private void updateIndexer(Cursor cursor) {
    427         if (cursor == null) {
    428             setIndexer(null);
    429             return;
    430         }
    431 
    432         Bundle bundle = cursor.getExtras();
    433         if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES)) {
    434             String sections[] =
    435                     bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
    436             int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
    437             setIndexer(new ContactsSectionIndexer(sections, counts));
    438         } else {
    439             setIndexer(null);
    440         }
    441     }
    442 
    443     @Override
    444     public int getViewTypeCount() {
    445         // We need a separate view type for each item type, plus another one for
    446         // each type with header, plus one for "other".
    447         return getItemViewTypeCount() * 2 + 1;
    448     }
    449 
    450     @Override
    451     public int getItemViewType(int partitionIndex, int position) {
    452         int type = super.getItemViewType(partitionIndex, position);
    453         if (!isUserProfile(position)
    454                 && isSectionHeaderDisplayEnabled()
    455                 && partitionIndex == getIndexedPartition()) {
    456             Placement placement = getItemPlacementInSection(position);
    457             return placement.firstInSection ? type : getItemViewTypeCount() + type;
    458         } else {
    459             return type;
    460         }
    461     }
    462 
    463     @Override
    464     public boolean isEmpty() {
    465         // TODO
    466 //        if (contactsListActivity.mProviderStatus != ProviderStatus.STATUS_NORMAL) {
    467 //            return true;
    468 //        }
    469 
    470         if (!mEmptyListEnabled) {
    471             return false;
    472         } else if (isSearchMode()) {
    473             return TextUtils.isEmpty(getQueryString());
    474         } else if (mLoading) {
    475             // We don't want the empty state to show when loading.
    476             return false;
    477         } else {
    478             return super.isEmpty();
    479         }
    480     }
    481 
    482     public boolean isLoading() {
    483         int count = getPartitionCount();
    484         for (int i = 0; i < count; i++) {
    485             Partition partition = getPartition(i);
    486             if (partition instanceof DirectoryPartition
    487                     && ((DirectoryPartition) partition).isLoading()) {
    488                 return true;
    489             }
    490         }
    491         return false;
    492     }
    493 
    494     public boolean areAllPartitionsEmpty() {
    495         int count = getPartitionCount();
    496         for (int i = 0; i < count; i++) {
    497             if (!isPartitionEmpty(i)) {
    498                 return false;
    499             }
    500         }
    501         return true;
    502     }
    503 
    504     /**
    505      * Changes visibility parameters for the default directory partition.
    506      */
    507     public void configureDefaultPartition(boolean showIfEmpty, boolean hasHeader) {
    508         int defaultPartitionIndex = -1;
    509         int count = getPartitionCount();
    510         for (int i = 0; i < count; i++) {
    511             Partition partition = getPartition(i);
    512             if (partition instanceof DirectoryPartition &&
    513                     ((DirectoryPartition)partition).getDirectoryId() == Directory.DEFAULT) {
    514                 defaultPartitionIndex = i;
    515                 break;
    516             }
    517         }
    518         if (defaultPartitionIndex != -1) {
    519             setShowIfEmpty(defaultPartitionIndex, showIfEmpty);
    520             setHasHeader(defaultPartitionIndex, hasHeader);
    521         }
    522     }
    523 
    524     @Override
    525     protected View newHeaderView(Context context, int partition, Cursor cursor,
    526             ViewGroup parent) {
    527         LayoutInflater inflater = LayoutInflater.from(context);
    528         return inflater.inflate(R.layout.directory_header, parent, false);
    529     }
    530 
    531     @Override
    532     protected void bindHeaderView(View view, int partitionIndex, Cursor cursor) {
    533         Partition partition = getPartition(partitionIndex);
    534         if (!(partition instanceof DirectoryPartition)) {
    535             return;
    536         }
    537 
    538         DirectoryPartition directoryPartition = (DirectoryPartition)partition;
    539         long directoryId = directoryPartition.getDirectoryId();
    540         TextView labelTextView = (TextView)view.findViewById(R.id.label);
    541         TextView displayNameTextView = (TextView)view.findViewById(R.id.display_name);
    542         if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
    543             labelTextView.setText(mDefaultFilterHeaderText);
    544             displayNameTextView.setText(null);
    545         } else {
    546             labelTextView.setText(R.string.directory_search_label);
    547             String directoryName = directoryPartition.getDisplayName();
    548             String displayName = !TextUtils.isEmpty(directoryName)
    549                     ? directoryName
    550                     : directoryPartition.getDirectoryType();
    551             displayNameTextView.setText(displayName);
    552         }
    553 
    554         TextView countText = (TextView)view.findViewById(R.id.count);
    555         if (directoryPartition.isLoading()) {
    556             countText.setText(R.string.search_results_searching);
    557         } else {
    558             int count = cursor == null ? 0 : cursor.getCount();
    559             if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE
    560                     && count >= getDirectoryResultLimit()) {
    561                 countText.setText(mContext.getString(
    562                         R.string.foundTooManyContacts, getDirectoryResultLimit()));
    563             } else {
    564                 countText.setText(getQuantityText(
    565                         count, R.string.listFoundAllContactsZero, R.plurals.searchFoundContacts));
    566             }
    567         }
    568     }
    569 
    570     /**
    571      * Checks whether the contact entry at the given position represents the user's profile.
    572      */
    573     protected boolean isUserProfile(int position) {
    574         // The profile only ever appears in the first position if it is present.  So if the position
    575         // is anything beyond 0, it can't be the profile.
    576         boolean isUserProfile = false;
    577         if (position == 0) {
    578             int partition = getPartitionForPosition(position);
    579             if (partition >= 0) {
    580                 // Save the old cursor position - the call to getItem() may modify the cursor
    581                 // position.
    582                 int offset = getCursor(partition).getPosition();
    583                 Cursor cursor = (Cursor) getItem(position);
    584                 if (cursor != null) {
    585                     int profileColumnIndex = cursor.getColumnIndex(Contacts.IS_USER_PROFILE);
    586                     if (profileColumnIndex != -1) {
    587                         isUserProfile = cursor.getInt(profileColumnIndex) == 1;
    588                     }
    589                     // Restore the old cursor position.
    590                     cursor.moveToPosition(offset);
    591                 }
    592             }
    593         }
    594         return isUserProfile;
    595     }
    596 
    597     // TODO: fix PluralRules to handle zero correctly and use Resources.getQuantityText directly
    598     public String getQuantityText(int count, int zeroResourceId, int pluralResourceId) {
    599         if (count == 0) {
    600             return getContext().getString(zeroResourceId);
    601         } else {
    602             String format = getContext().getResources()
    603                     .getQuantityText(pluralResourceId, count).toString();
    604             return String.format(format, count);
    605         }
    606     }
    607 
    608     public boolean isPhotoSupported(int partitionIndex) {
    609         Partition partition = getPartition(partitionIndex);
    610         if (partition instanceof DirectoryPartition) {
    611             return ((DirectoryPartition) partition).isPhotoSupported();
    612         }
    613         return true;
    614     }
    615 
    616     /**
    617      * Returns the currently selected filter.
    618      */
    619     public ContactListFilter getFilter() {
    620         return mFilter;
    621     }
    622 
    623     public void setFilter(ContactListFilter filter) {
    624         mFilter = filter;
    625     }
    626 
    627     // TODO: move sharable logic (bindXX() methods) to here with extra arguments
    628 
    629     /**
    630      * Loads the photo for the quick contact view and assigns the contact uri.
    631      * @param photoIdColumn Index of the photo id column
    632      * @param photoUriColumn Index of the photo uri column. Optional: Can be -1
    633      * @param contactIdColumn Index of the contact id column
    634      * @param lookUpKeyColumn Index of the lookup key column
    635      */
    636     protected void bindQuickContact(final ContactListItemView view, int partitionIndex,
    637             Cursor cursor, int photoIdColumn, int photoUriColumn, int contactIdColumn,
    638             int lookUpKeyColumn) {
    639         long photoId = 0;
    640         if (!cursor.isNull(photoIdColumn)) {
    641             photoId = cursor.getLong(photoIdColumn);
    642         }
    643 
    644         QuickContactBadge quickContact = view.getQuickContact();
    645         quickContact.assignContactUri(
    646                 getContactUri(partitionIndex, cursor, contactIdColumn, lookUpKeyColumn));
    647 
    648         if (photoId != 0 || photoUriColumn == -1) {
    649             getPhotoLoader().loadThumbnail(quickContact, photoId, mDarkTheme);
    650         } else {
    651             final String photoUriString = cursor.getString(photoUriColumn);
    652             final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
    653             getPhotoLoader().loadPhoto(quickContact, photoUri, -1, mDarkTheme);
    654         }
    655 
    656     }
    657 
    658     protected Uri getContactUri(int partitionIndex, Cursor cursor,
    659             int contactIdColumn, int lookUpKeyColumn) {
    660         long contactId = cursor.getLong(contactIdColumn);
    661         String lookupKey = cursor.getString(lookUpKeyColumn);
    662         Uri uri = Contacts.getLookupUri(contactId, lookupKey);
    663         long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
    664         if (directoryId != Directory.DEFAULT) {
    665             uri = uri.buildUpon().appendQueryParameter(
    666                     ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
    667         }
    668         return uri;
    669     }
    670 
    671     public void setContactsCount(String count) {
    672         mContactsCount = count;
    673     }
    674 
    675     public String getContactsCount() {
    676         return mContactsCount;
    677     }
    678 }
    679