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