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