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.ContentUris;
     19 import android.content.Context;
     20 import android.content.CursorLoader;
     21 import android.database.Cursor;
     22 import android.net.Uri;
     23 import android.net.Uri.Builder;
     24 import android.provider.ContactsContract;
     25 import android.provider.ContactsContract.CommonDataKinds.Callable;
     26 import android.provider.ContactsContract.CommonDataKinds.Phone;
     27 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
     28 import android.provider.ContactsContract.Contacts;
     29 import android.provider.ContactsContract.Data;
     30 import android.provider.ContactsContract.Directory;
     31 import android.telephony.PhoneNumberUtils;
     32 import android.text.TextUtils;
     33 import android.util.Log;
     34 import android.view.View;
     35 import android.view.ViewGroup;
     36 
     37 import com.android.contacts.common.GeoUtil;
     38 import com.android.contacts.common.R;
     39 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
     40 import com.android.contacts.common.extensions.ExtendedPhoneDirectoriesManager;
     41 import com.android.contacts.common.extensions.ExtensionsFactory;
     42 import com.android.contacts.common.preference.ContactsPreferences;
     43 import com.android.contacts.common.util.Constants;
     44 
     45 import java.util.ArrayList;
     46 import java.util.List;
     47 
     48 /**
     49  * A cursor adapter for the {@link Phone#CONTENT_ITEM_TYPE} and
     50  * {@link SipAddress#CONTENT_ITEM_TYPE}.
     51  *
     52  * By default this adapter just handles phone numbers. When {@link #setUseCallableUri(boolean)} is
     53  * called with "true", this adapter starts handling SIP addresses too, by using {@link Callable}
     54  * API instead of {@link Phone}.
     55  */
     56 public class PhoneNumberListAdapter extends ContactEntryListAdapter {
     57 
     58     private static final String TAG = PhoneNumberListAdapter.class.getSimpleName();
     59 
     60     // A list of extended directories to add to the directories from the database
     61     private final List<DirectoryPartition> mExtendedDirectories;
     62 
     63     // Extended directories will have ID's that are higher than any of the id's from the database.
     64     // Thi sis so that we can identify them and set them up properly. If no extended directories
     65     // exist, this will be Long.MAX_VALUE
     66     private long mFirstExtendedDirectoryId = Long.MAX_VALUE;
     67 
     68     public static class PhoneQuery {
     69 
     70         /**
     71          * Optional key used as part of a JSON lookup key to specify an analytics category
     72          * associated with the row.
     73          */
     74         public static final String ANALYTICS_CATEGORY = "analytics_category";
     75 
     76         /**
     77          * Optional key used as part of a JSON lookup key to specify an analytics action associated
     78          * with the row.
     79          */
     80         public static final String ANALYTICS_ACTION = "analytics_action";
     81 
     82         /**
     83          * Optional key used as part of a JSON lookup key to specify an analytics value associated
     84          * with the row.
     85          */
     86         public static final String ANALYTICS_VALUE = "analytics_value";
     87 
     88         public static final String[] PROJECTION_PRIMARY = new String[] {
     89             Phone._ID,                          // 0
     90             Phone.TYPE,                         // 1
     91             Phone.LABEL,                        // 2
     92             Phone.NUMBER,                       // 3
     93             Phone.CONTACT_ID,                   // 4
     94             Phone.LOOKUP_KEY,                   // 5
     95             Phone.PHOTO_ID,                     // 6
     96             Phone.DISPLAY_NAME_PRIMARY,         // 7
     97             Phone.PHOTO_THUMBNAIL_URI,          // 8
     98         };
     99 
    100         public static final String[] PROJECTION_ALTERNATIVE = new String[] {
    101             Phone._ID,                          // 0
    102             Phone.TYPE,                         // 1
    103             Phone.LABEL,                        // 2
    104             Phone.NUMBER,                       // 3
    105             Phone.CONTACT_ID,                   // 4
    106             Phone.LOOKUP_KEY,                   // 5
    107             Phone.PHOTO_ID,                     // 6
    108             Phone.DISPLAY_NAME_ALTERNATIVE,     // 7
    109             Phone.PHOTO_THUMBNAIL_URI,          // 8
    110         };
    111 
    112         public static final int PHONE_ID                = 0;
    113         public static final int PHONE_TYPE              = 1;
    114         public static final int PHONE_LABEL             = 2;
    115         public static final int PHONE_NUMBER            = 3;
    116         public static final int CONTACT_ID              = 4;
    117         public static final int LOOKUP_KEY              = 5;
    118         public static final int PHOTO_ID                = 6;
    119         public static final int DISPLAY_NAME            = 7;
    120         public static final int PHOTO_URI               = 8;
    121     }
    122 
    123     private static final String IGNORE_NUMBER_TOO_LONG_CLAUSE =
    124             "length(" + Phone.NUMBER + ") < 1000";
    125 
    126     private final CharSequence mUnknownNameText;
    127     private final String mCountryIso;
    128 
    129     private ContactListItemView.PhotoPosition mPhotoPosition;
    130 
    131     private boolean mUseCallableUri;
    132 
    133     public PhoneNumberListAdapter(Context context) {
    134         super(context);
    135         setDefaultFilterHeaderText(R.string.list_filter_phones);
    136         mUnknownNameText = context.getText(android.R.string.unknownName);
    137         mCountryIso = GeoUtil.getCurrentCountryIso(context);
    138 
    139         final ExtendedPhoneDirectoriesManager manager
    140                 = ExtensionsFactory.getExtendedPhoneDirectoriesManager();
    141         if (manager != null) {
    142             mExtendedDirectories = manager.getExtendedDirectories(mContext);
    143         } else {
    144             // Empty list to avoid sticky NPE's
    145             mExtendedDirectories = new ArrayList<DirectoryPartition>();
    146         }
    147     }
    148 
    149     protected CharSequence getUnknownNameText() {
    150         return mUnknownNameText;
    151     }
    152 
    153     @Override
    154     public void configureLoader(CursorLoader loader, long directoryId) {
    155         String query = getQueryString();
    156         if (query == null) {
    157             query = "";
    158         }
    159         if (isExtendedDirectory(directoryId)) {
    160             final DirectoryPartition directory = getExtendedDirectoryFromId(directoryId);
    161             final String contentUri = directory.getContentUri();
    162             if (contentUri == null) {
    163                 throw new IllegalStateException("Extended directory must have a content URL: "
    164                         + directory);
    165             }
    166             final Builder builder = Uri.parse(contentUri).buildUpon();
    167             builder.appendPath(query);
    168             builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
    169                     String.valueOf(getDirectoryResultLimit(directory)));
    170             loader.setUri(builder.build());
    171             loader.setProjection(PhoneQuery.PROJECTION_PRIMARY);
    172         } else {
    173             final boolean isRemoteDirectoryQuery = isRemoteDirectory(directoryId);
    174             final Builder builder;
    175             if (isSearchMode()) {
    176                 final Uri baseUri;
    177                 if (isRemoteDirectoryQuery) {
    178                     baseUri = Phone.CONTENT_FILTER_URI;
    179                 } else if (mUseCallableUri) {
    180                     baseUri = Callable.CONTENT_FILTER_URI;
    181                 } else {
    182                     baseUri = Phone.CONTENT_FILTER_URI;
    183                 }
    184                 builder = baseUri.buildUpon();
    185                 builder.appendPath(query);      // Builder will encode the query
    186                 builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
    187                         String.valueOf(directoryId));
    188                 if (isRemoteDirectoryQuery) {
    189                     builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
    190                             String.valueOf(getDirectoryResultLimit(getDirectoryById(directoryId))));
    191                 }
    192             } else {
    193                 final Uri baseUri = mUseCallableUri ? Callable.CONTENT_URI : Phone.CONTENT_URI;
    194                 builder = baseUri.buildUpon().appendQueryParameter(
    195                         ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT));
    196                 if (isSectionHeaderDisplayEnabled()) {
    197                     builder.appendQueryParameter(Phone.EXTRA_ADDRESS_BOOK_INDEX, "true");
    198                 }
    199                 applyFilter(loader, builder, directoryId, getFilter());
    200             }
    201 
    202             // Ignore invalid phone numbers that are too long. These can potentially cause freezes
    203             // in the UI and there is no reason to display them.
    204             final String prevSelection = loader.getSelection();
    205             final String newSelection;
    206             if (!TextUtils.isEmpty(prevSelection)) {
    207                 newSelection = prevSelection + " AND " + IGNORE_NUMBER_TOO_LONG_CLAUSE;
    208             } else {
    209                 newSelection = IGNORE_NUMBER_TOO_LONG_CLAUSE;
    210             }
    211             loader.setSelection(newSelection);
    212 
    213             // Remove duplicates when it is possible.
    214             builder.appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true");
    215             loader.setUri(builder.build());
    216 
    217             // TODO a projection that includes the search snippet
    218             if (getContactNameDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
    219                 loader.setProjection(PhoneQuery.PROJECTION_PRIMARY);
    220             } else {
    221                 loader.setProjection(PhoneQuery.PROJECTION_ALTERNATIVE);
    222             }
    223 
    224             if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
    225                 loader.setSortOrder(Phone.SORT_KEY_PRIMARY);
    226             } else {
    227                 loader.setSortOrder(Phone.SORT_KEY_ALTERNATIVE);
    228             }
    229         }
    230     }
    231 
    232     protected boolean isExtendedDirectory(long directoryId) {
    233         return directoryId >= mFirstExtendedDirectoryId;
    234     }
    235 
    236     private DirectoryPartition getExtendedDirectoryFromId(long directoryId) {
    237         final int directoryIndex = (int) (directoryId - mFirstExtendedDirectoryId);
    238         return mExtendedDirectories.get(directoryIndex);
    239     }
    240 
    241     /**
    242      * Configure {@code loader} and {@code uriBuilder} according to {@code directoryId} and {@code
    243      * filter}.
    244      */
    245     private void applyFilter(CursorLoader loader, Uri.Builder uriBuilder, long directoryId,
    246             ContactListFilter filter) {
    247         if (filter == null || directoryId != Directory.DEFAULT) {
    248             return;
    249         }
    250 
    251         final StringBuilder selection = new StringBuilder();
    252         final List<String> selectionArgs = new ArrayList<String>();
    253 
    254         switch (filter.filterType) {
    255             case ContactListFilter.FILTER_TYPE_CUSTOM: {
    256                 selection.append(Contacts.IN_VISIBLE_GROUP + "=1");
    257                 selection.append(" AND " + Contacts.HAS_PHONE_NUMBER + "=1");
    258                 break;
    259             }
    260             case ContactListFilter.FILTER_TYPE_ACCOUNT: {
    261                 filter.addAccountQueryParameterToUrl(uriBuilder);
    262                 break;
    263             }
    264             case ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS:
    265             case ContactListFilter.FILTER_TYPE_DEFAULT:
    266                 break; // No selection needed.
    267             case ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY:
    268                 break; // This adapter is always "phone only", so no selection needed either.
    269             default:
    270                 Log.w(TAG, "Unsupported filter type came " +
    271                         "(type: " + filter.filterType + ", toString: " + filter + ")" +
    272                         " showing all contacts.");
    273                 // No selection.
    274                 break;
    275         }
    276         loader.setSelection(selection.toString());
    277         loader.setSelectionArgs(selectionArgs.toArray(new String[0]));
    278     }
    279 
    280     @Override
    281     public String getContactDisplayName(int position) {
    282         return ((Cursor) getItem(position)).getString(PhoneQuery.DISPLAY_NAME);
    283     }
    284 
    285     public String getPhoneNumber(int position) {
    286         final Cursor item = (Cursor)getItem(position);
    287         return item != null ? item.getString(PhoneQuery.PHONE_NUMBER) : null;
    288     }
    289 
    290     /**
    291      * Builds a {@link Data#CONTENT_URI} for the given cursor position.
    292      *
    293      * @return Uri for the data. may be null if the cursor is not ready.
    294      */
    295     public Uri getDataUri(int position) {
    296         final int partitionIndex = getPartitionForPosition(position);
    297         final Cursor item = (Cursor)getItem(position);
    298         return item != null ? getDataUri(partitionIndex, item) : null;
    299     }
    300 
    301     public Uri getDataUri(int partitionIndex, Cursor cursor) {
    302         final long directoryId =
    303                 ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
    304         if (!isRemoteDirectory(directoryId)) {
    305             final long phoneId = cursor.getLong(PhoneQuery.PHONE_ID);
    306             return ContentUris.withAppendedId(Data.CONTENT_URI, phoneId);
    307         }
    308         return null;
    309     }
    310 
    311     /**
    312      * Retrieves the lookup key for the given cursor position.
    313      *
    314      * @param position The cursor position.
    315      * @return The lookup key.
    316      */
    317     public String getLookupKey(int position) {
    318         final Cursor item = (Cursor)getItem(position);
    319         return item != null ? item.getString(PhoneQuery.LOOKUP_KEY) : null;
    320     }
    321 
    322     @Override
    323     protected ContactListItemView newView(
    324             Context context, int partition, Cursor cursor, int position, ViewGroup parent) {
    325         ContactListItemView view = super.newView(context, partition, cursor, position, parent);
    326         view.setUnknownNameText(mUnknownNameText);
    327         view.setQuickContactEnabled(isQuickContactEnabled());
    328         view.setPhotoPosition(mPhotoPosition);
    329         return view;
    330     }
    331 
    332     protected void setHighlight(ContactListItemView view, Cursor cursor) {
    333         view.setHighlightedPrefix(isSearchMode() ? getUpperCaseQueryString() : null);
    334     }
    335 
    336     // Override default, which would return number of phone numbers, so we
    337     // instead return number of contacts.
    338     @Override
    339     protected int getResultCount(Cursor cursor) {
    340         if (cursor == null) {
    341             return 0;
    342         }
    343         cursor.moveToPosition(-1);
    344         long curContactId = -1;
    345         int numContacts = 0;
    346         while(cursor.moveToNext()) {
    347             final long contactId = cursor.getLong(PhoneQuery.CONTACT_ID);
    348             if (contactId != curContactId) {
    349                 curContactId = contactId;
    350                 ++numContacts;
    351             }
    352         }
    353         return numContacts;
    354     }
    355 
    356     @Override
    357     protected void bindView(View itemView, int partition, Cursor cursor, int position) {
    358         super.bindView(itemView, partition, cursor, position);
    359         ContactListItemView view = (ContactListItemView)itemView;
    360 
    361         setHighlight(view, cursor);
    362 
    363         // Look at elements before and after this position, checking if contact IDs are same.
    364         // If they have one same contact ID, it means they can be grouped.
    365         //
    366         // In one group, only the first entry will show its photo and its name, and the other
    367         // entries in the group show just their data (e.g. phone number, email address).
    368         cursor.moveToPosition(position);
    369         boolean isFirstEntry = true;
    370         boolean showBottomDivider = true;
    371         final long currentContactId = cursor.getLong(PhoneQuery.CONTACT_ID);
    372         if (cursor.moveToPrevious() && !cursor.isBeforeFirst()) {
    373             final long previousContactId = cursor.getLong(PhoneQuery.CONTACT_ID);
    374             if (currentContactId == previousContactId) {
    375                 isFirstEntry = false;
    376             }
    377         }
    378         cursor.moveToPosition(position);
    379         if (cursor.moveToNext() && !cursor.isAfterLast()) {
    380             final long nextContactId = cursor.getLong(PhoneQuery.CONTACT_ID);
    381             if (currentContactId == nextContactId) {
    382                 // The following entry should be in the same group, which means we don't want a
    383                 // divider between them.
    384                 // TODO: we want a different divider than the divider between groups. Just hiding
    385                 // this divider won't be enough.
    386                 showBottomDivider = false;
    387             }
    388         }
    389         cursor.moveToPosition(position);
    390 
    391         bindViewId(view, cursor, PhoneQuery.PHONE_ID);
    392 
    393         bindSectionHeaderAndDivider(view, position);
    394         if (isFirstEntry) {
    395             bindName(view, cursor);
    396             if (isQuickContactEnabled()) {
    397                 bindQuickContact(view, partition, cursor, PhoneQuery.PHOTO_ID,
    398                         PhoneQuery.PHOTO_URI, PhoneQuery.CONTACT_ID,
    399                         PhoneQuery.LOOKUP_KEY, PhoneQuery.DISPLAY_NAME);
    400             } else {
    401                 if (getDisplayPhotos()) {
    402                     bindPhoto(view, partition, cursor);
    403                 }
    404             }
    405         } else {
    406             unbindName(view);
    407 
    408             view.removePhotoView(true, false);
    409         }
    410 
    411         final DirectoryPartition directory = (DirectoryPartition) getPartition(partition);
    412         bindPhoneNumber(view, cursor, directory.isDisplayNumber());
    413     }
    414 
    415     protected void bindPhoneNumber(ContactListItemView view, Cursor cursor, boolean displayNumber) {
    416         CharSequence label = null;
    417         if (displayNumber &&  !cursor.isNull(PhoneQuery.PHONE_TYPE)) {
    418             final int type = cursor.getInt(PhoneQuery.PHONE_TYPE);
    419             final String customLabel = cursor.getString(PhoneQuery.PHONE_LABEL);
    420 
    421             // TODO cache
    422             label = Phone.getTypeLabel(getContext().getResources(), type, customLabel);
    423         }
    424         view.setLabel(label);
    425         final String text;
    426         if (displayNumber) {
    427             text = cursor.getString(PhoneQuery.PHONE_NUMBER);
    428         } else {
    429             // Display phone label. If that's null, display geocoded location for the number
    430             final String phoneLabel = cursor.getString(PhoneQuery.PHONE_LABEL);
    431             if (phoneLabel != null) {
    432                 text = phoneLabel;
    433             } else {
    434                 final String phoneNumber = cursor.getString(PhoneQuery.PHONE_NUMBER);
    435                 text = GeoUtil.getGeocodedLocationFor(mContext, phoneNumber);
    436             }
    437         }
    438         view.setPhoneNumber(text, mCountryIso);
    439     }
    440 
    441     protected void bindSectionHeaderAndDivider(final ContactListItemView view, int position) {
    442         if (isSectionHeaderDisplayEnabled()) {
    443             Placement placement = getItemPlacementInSection(position);
    444             view.setSectionHeader(placement.firstInSection ? placement.sectionHeader : null);
    445         } else {
    446             view.setSectionHeader(null);
    447         }
    448     }
    449 
    450     protected void bindName(final ContactListItemView view, Cursor cursor) {
    451         view.showDisplayName(cursor, PhoneQuery.DISPLAY_NAME, getContactNameDisplayOrder());
    452         // Note: we don't show phonetic names any more (see issue 5265330)
    453     }
    454 
    455     protected void unbindName(final ContactListItemView view) {
    456         view.hideDisplayName();
    457     }
    458 
    459     protected void bindPhoto(final ContactListItemView view, int partitionIndex, Cursor cursor) {
    460         if (!isPhotoSupported(partitionIndex)) {
    461             view.removePhotoView();
    462             return;
    463         }
    464 
    465         long photoId = 0;
    466         if (!cursor.isNull(PhoneQuery.PHOTO_ID)) {
    467             photoId = cursor.getLong(PhoneQuery.PHOTO_ID);
    468         }
    469 
    470         if (photoId != 0) {
    471             getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false,
    472                     getCircularPhotos(), null);
    473         } else {
    474             final String photoUriString = cursor.getString(PhoneQuery.PHOTO_URI);
    475             final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
    476 
    477             DefaultImageRequest request = null;
    478             if (photoUri == null) {
    479                 final String displayName = cursor.getString(PhoneQuery.DISPLAY_NAME);
    480                 final String lookupKey = cursor.getString(PhoneQuery.LOOKUP_KEY);
    481                 request = new DefaultImageRequest(displayName, lookupKey, getCircularPhotos());
    482             }
    483             getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false,
    484                     getCircularPhotos(), request);
    485         }
    486     }
    487 
    488     public void setPhotoPosition(ContactListItemView.PhotoPosition photoPosition) {
    489         mPhotoPosition = photoPosition;
    490     }
    491 
    492     public ContactListItemView.PhotoPosition getPhotoPosition() {
    493         return mPhotoPosition;
    494     }
    495 
    496     public void setUseCallableUri(boolean useCallableUri) {
    497         mUseCallableUri = useCallableUri;
    498     }
    499 
    500     public boolean usesCallableUri() {
    501         return mUseCallableUri;
    502     }
    503 
    504     /**
    505      * Override base implementation to inject extended directories between local & remote
    506      * directories. This is done in the following steps:
    507      * 1. Call base implementation to add directories from the cursor.
    508      * 2. Iterate all base directories and establish the following information:
    509      *   a. The highest directory id so that we can assign unused id's to the extended directories.
    510      *   b. The index of the last non-remote directory. This is where we will insert extended
    511      *      directories.
    512      * 3. Iterate the extended directories and for each one, assign an ID and insert it in the
    513      *    proper location.
    514      */
    515     @Override
    516     public void changeDirectories(Cursor cursor) {
    517         super.changeDirectories(cursor);
    518         if (getDirectorySearchMode() == DirectoryListLoader.SEARCH_MODE_NONE) {
    519             return;
    520         }
    521         final int numExtendedDirectories = mExtendedDirectories.size();
    522         if (getPartitionCount() == cursor.getCount() + numExtendedDirectories) {
    523             // already added all directories;
    524             return;
    525         }
    526         //
    527         mFirstExtendedDirectoryId = Long.MAX_VALUE;
    528         if (numExtendedDirectories > 0) {
    529             // The Directory.LOCAL_INVISIBLE is not in the cursor but we can't reuse it's
    530             // "special" ID.
    531             long maxId = Directory.LOCAL_INVISIBLE;
    532             int insertIndex = 0;
    533             for (int i = 0, n = getPartitionCount(); i < n; i++) {
    534                 final DirectoryPartition partition = (DirectoryPartition) getPartition(i);
    535                 final long id = partition.getDirectoryId();
    536                 if (id > maxId) {
    537                     maxId = id;
    538                 }
    539                 if (!isRemoteDirectory(id)) {
    540                     // assuming remote directories come after local, we will end up with the index
    541                     // where we should insert extended directories. This also works if there are no
    542                     // remote directories at all.
    543                     insertIndex = i + 1;
    544                 }
    545             }
    546             // Extended directories ID's cannot collide with base directories
    547             mFirstExtendedDirectoryId = maxId + 1;
    548             for (int i = 0; i < numExtendedDirectories; i++) {
    549                 final long id = mFirstExtendedDirectoryId + i;
    550                 final DirectoryPartition directory = mExtendedDirectories.get(i);
    551                 if (getPartitionByDirectoryId(id) == -1) {
    552                     addPartition(insertIndex, directory);
    553                     directory.setDirectoryId(id);
    554                 }
    555             }
    556         }
    557     }
    558 
    559     protected Uri getContactUri(int partitionIndex, Cursor cursor,
    560             int contactIdColumn, int lookUpKeyColumn) {
    561         final DirectoryPartition directory = (DirectoryPartition) getPartition(partitionIndex);
    562         final long directoryId = directory.getDirectoryId();
    563         if (!isExtendedDirectory(directoryId)) {
    564             return super.getContactUri(partitionIndex, cursor, contactIdColumn, lookUpKeyColumn);
    565         }
    566         return Contacts.CONTENT_LOOKUP_URI.buildUpon()
    567                 .appendPath(Constants.LOOKUP_URI_ENCODED)
    568                 .appendQueryParameter(Directory.DISPLAY_NAME, directory.getLabel())
    569                 .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
    570                         String.valueOf(directoryId))
    571                 .encodedFragment(cursor.getString(lookUpKeyColumn))
    572                 .build();
    573     }
    574 }
    575