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