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