Home | History | Annotate | Download | only in chips
      1 /*
      2  * Copyright (C) 2011 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.ex.chips;
     18 
     19 import android.accounts.Account;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.database.Cursor;
     23 import android.database.MatrixCursor;
     24 import android.graphics.drawable.StateListDrawable;
     25 import android.net.Uri;
     26 import android.provider.ContactsContract;
     27 import android.provider.ContactsContract.Contacts;
     28 import android.text.TextUtils;
     29 import android.text.util.Rfc822Token;
     30 import android.text.util.Rfc822Tokenizer;
     31 import android.util.Log;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.widget.CursorAdapter;
     35 
     36 import com.android.ex.chips.BaseRecipientAdapter.DirectoryListQuery;
     37 import com.android.ex.chips.BaseRecipientAdapter.DirectorySearchParams;
     38 import com.android.ex.chips.DropdownChipLayouter.AdapterType;
     39 import com.android.ex.chips.Queries.Query;
     40 
     41 import java.util.ArrayList;
     42 import java.util.HashMap;
     43 import java.util.HashSet;
     44 import java.util.List;
     45 import java.util.Map;
     46 import java.util.Set;
     47 
     48 /**
     49  * RecipientAlternatesAdapter backs the RecipientEditTextView for managing contacts
     50  * queried by email or by phone number.
     51  */
     52 public class RecipientAlternatesAdapter extends CursorAdapter {
     53     public static final int MAX_LOOKUPS = 50;
     54 
     55     private final long mCurrentId;
     56 
     57     private int mCheckedItemPosition = -1;
     58 
     59     private OnCheckedItemChangedListener mCheckedItemChangedListener;
     60 
     61     private static final String TAG = "RecipAlternates";
     62 
     63     public static final int QUERY_TYPE_EMAIL = 0;
     64     public static final int QUERY_TYPE_PHONE = 1;
     65     private final Long mDirectoryId;
     66     private DropdownChipLayouter mDropdownChipLayouter;
     67     private final StateListDrawable mDeleteDrawable;
     68 
     69     private static final Map<String, String> sCorrectedPhotoUris = new HashMap<String, String>();
     70 
     71     public interface RecipientMatchCallback {
     72         public void matchesFound(Map<String, RecipientEntry> results);
     73         /**
     74          * Called with all addresses that could not be resolved to valid recipients.
     75          */
     76         public void matchesNotFound(Set<String> unfoundAddresses);
     77     }
     78 
     79     public static void getMatchingRecipients(Context context, BaseRecipientAdapter adapter,
     80             ArrayList<String> inAddresses, Account account, RecipientMatchCallback callback) {
     81         getMatchingRecipients(context, adapter, inAddresses, QUERY_TYPE_EMAIL, account, callback);
     82     }
     83 
     84     /**
     85      * Get a HashMap of address to RecipientEntry that contains all contact
     86      * information for a contact with the provided address, if one exists. This
     87      * may block the UI, so run it in an async task.
     88      *
     89      * @param context Context.
     90      * @param inAddresses Array of addresses on which to perform the lookup.
     91      * @param callback RecipientMatchCallback called when a match or matches are found.
     92      */
     93     public static void getMatchingRecipients(Context context, BaseRecipientAdapter adapter,
     94             ArrayList<String> inAddresses, int addressType, Account account,
     95             RecipientMatchCallback callback) {
     96         Queries.Query query;
     97         if (addressType == QUERY_TYPE_EMAIL) {
     98             query = Queries.EMAIL;
     99         } else {
    100             query = Queries.PHONE;
    101         }
    102         int addressesSize = Math.min(MAX_LOOKUPS, inAddresses.size());
    103         HashSet<String> addresses = new HashSet<String>();
    104         StringBuilder bindString = new StringBuilder();
    105         // Create the "?" string and set up arguments.
    106         for (int i = 0; i < addressesSize; i++) {
    107             Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses.get(i).toLowerCase());
    108             addresses.add(tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i));
    109             bindString.append("?");
    110             if (i < addressesSize - 1) {
    111                 bindString.append(",");
    112             }
    113         }
    114 
    115         if (Log.isLoggable(TAG, Log.DEBUG)) {
    116             Log.d(TAG, "Doing reverse lookup for " + addresses.toString());
    117         }
    118 
    119         String[] addressArray = new String[addresses.size()];
    120         addresses.toArray(addressArray);
    121         HashMap<String, RecipientEntry> recipientEntries = null;
    122         Cursor c = null;
    123 
    124         try {
    125             c = context.getContentResolver().query(
    126                     query.getContentUri(),
    127                     query.getProjection(),
    128                     query.getProjection()[Queries.Query.DESTINATION] + " IN ("
    129                             + bindString.toString() + ")", addressArray, null);
    130             recipientEntries = processContactEntries(c, null /* directoryId */);
    131             callback.matchesFound(recipientEntries);
    132         } finally {
    133             if (c != null) {
    134                 c.close();
    135             }
    136         }
    137 
    138         final Set<String> matchesNotFound = new HashSet<String>();
    139 
    140         getMatchingRecipientsFromDirectoryQueries(context, recipientEntries,
    141                 addresses, account, matchesNotFound, query, callback);
    142 
    143         getMatchingRecipientsFromExtensionMatcher(adapter, matchesNotFound, callback);
    144     }
    145 
    146     public static void getMatchingRecipientsFromDirectoryQueries(Context context,
    147             Map<String, RecipientEntry> recipientEntries, Set<String> addresses,
    148             Account account, Set<String> matchesNotFound,
    149             RecipientMatchCallback callback) {
    150         getMatchingRecipientsFromDirectoryQueries(
    151                 context, recipientEntries, addresses, account,
    152                 matchesNotFound, Queries.EMAIL, callback);
    153     }
    154 
    155     private static void getMatchingRecipientsFromDirectoryQueries(Context context,
    156             Map<String, RecipientEntry> recipientEntries, Set<String> addresses,
    157             Account account, Set<String> matchesNotFound, Queries.Query query,
    158             RecipientMatchCallback callback) {
    159         // See if any entries did not resolve; if so, we need to check other
    160         // directories
    161 
    162         if (recipientEntries.size() < addresses.size()) {
    163             final List<DirectorySearchParams> paramsList;
    164             Cursor directoryCursor = null;
    165             try {
    166                 directoryCursor = context.getContentResolver().query(DirectoryListQuery.URI,
    167                         DirectoryListQuery.PROJECTION, null, null, null);
    168                 if (directoryCursor == null) {
    169                     paramsList = null;
    170                 } else {
    171                     paramsList = BaseRecipientAdapter.setupOtherDirectories(context,
    172                             directoryCursor, account);
    173                 }
    174             } finally {
    175                 if (directoryCursor != null) {
    176                     directoryCursor.close();
    177                 }
    178             }
    179             // Run a directory query for each unmatched recipient.
    180             HashSet<String> unresolvedAddresses = new HashSet<String>();
    181             for (String address : addresses) {
    182                 if (!recipientEntries.containsKey(address)) {
    183                     unresolvedAddresses.add(address);
    184                 }
    185             }
    186 
    187             matchesNotFound.addAll(unresolvedAddresses);
    188 
    189             if (paramsList != null) {
    190                 Cursor directoryContactsCursor = null;
    191                 for (String unresolvedAddress : unresolvedAddresses) {
    192                     Long directoryId = null;
    193                     for (int i = 0; i < paramsList.size(); i++) {
    194                         try {
    195                             directoryContactsCursor = doQuery(unresolvedAddress, 1,
    196                                     paramsList.get(i).directoryId, account,
    197                                     context.getContentResolver(), query);
    198                         } finally {
    199                             if (directoryContactsCursor != null
    200                                     && directoryContactsCursor.getCount() == 0) {
    201                                 directoryContactsCursor.close();
    202                                 directoryContactsCursor = null;
    203                             } else {
    204                                 directoryId = paramsList.get(i).directoryId;
    205                                 break;
    206                             }
    207                         }
    208                     }
    209                     if (directoryContactsCursor != null) {
    210                         try {
    211                             final Map<String, RecipientEntry> entries =
    212                                     processContactEntries(directoryContactsCursor, directoryId);
    213 
    214                             for (final String address : entries.keySet()) {
    215                                 matchesNotFound.remove(address);
    216                             }
    217 
    218                             callback.matchesFound(entries);
    219                         } finally {
    220                             directoryContactsCursor.close();
    221                         }
    222                     }
    223                 }
    224             }
    225         }
    226     }
    227 
    228     public static void getMatchingRecipientsFromExtensionMatcher(BaseRecipientAdapter adapter,
    229             Set<String> matchesNotFound, RecipientMatchCallback callback) {
    230         // If no matches found in contact provider or the directories, try the extension
    231         // matcher.
    232         // todo (aalbert): This whole method needs to be in the adapter?
    233         if (adapter != null) {
    234             final Map<String, RecipientEntry> entries =
    235                     adapter.getMatchingRecipients(matchesNotFound);
    236             if (entries != null && entries.size() > 0) {
    237                 callback.matchesFound(entries);
    238                 for (final String address : entries.keySet()) {
    239                     matchesNotFound.remove(address);
    240                 }
    241             }
    242         }
    243         callback.matchesNotFound(matchesNotFound);
    244     }
    245 
    246     private static HashMap<String, RecipientEntry> processContactEntries(Cursor c,
    247             Long directoryId) {
    248         HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>();
    249         if (c != null && c.moveToFirst()) {
    250             do {
    251                 String address = c.getString(Queries.Query.DESTINATION);
    252 
    253                 final RecipientEntry newRecipientEntry = RecipientEntry.constructTopLevelEntry(
    254                         c.getString(Queries.Query.NAME),
    255                         c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
    256                         c.getString(Queries.Query.DESTINATION),
    257                         c.getInt(Queries.Query.DESTINATION_TYPE),
    258                         c.getString(Queries.Query.DESTINATION_LABEL),
    259                         c.getLong(Queries.Query.CONTACT_ID),
    260                         directoryId,
    261                         c.getLong(Queries.Query.DATA_ID),
    262                         c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
    263                         true,
    264                         c.getString(Queries.Query.LOOKUP_KEY));
    265 
    266                 /*
    267                  * In certain situations, we may have two results for one address, where one of the
    268                  * results is just the email address, and the other has a name and photo, so we want
    269                  * to use the better one.
    270                  */
    271                 final RecipientEntry recipientEntry =
    272                         getBetterRecipient(recipientEntries.get(address), newRecipientEntry);
    273 
    274                 recipientEntries.put(address, recipientEntry);
    275                 if (Log.isLoggable(TAG, Log.DEBUG)) {
    276                     Log.d(TAG, "Received reverse look up information for " + address
    277                             + " RESULTS: "
    278                             + " NAME : " + c.getString(Queries.Query.NAME)
    279                             + " CONTACT ID : " + c.getLong(Queries.Query.CONTACT_ID)
    280                             + " ADDRESS :" + c.getString(Queries.Query.DESTINATION));
    281                 }
    282             } while (c.moveToNext());
    283         }
    284         return recipientEntries;
    285     }
    286 
    287     /**
    288      * Given two {@link RecipientEntry}s for the same email address, this will return the one that
    289      * contains more complete information for display purposes. Defaults to <code>entry2</code> if
    290      * no significant differences are found.
    291      */
    292     static RecipientEntry getBetterRecipient(final RecipientEntry entry1,
    293             final RecipientEntry entry2) {
    294         // If only one has passed in, use it
    295         if (entry2 == null) {
    296             return entry1;
    297         }
    298 
    299         if (entry1 == null) {
    300             return entry2;
    301         }
    302 
    303         // If only one has a display name, use it
    304         if (!TextUtils.isEmpty(entry1.getDisplayName())
    305                 && TextUtils.isEmpty(entry2.getDisplayName())) {
    306             return entry1;
    307         }
    308 
    309         if (!TextUtils.isEmpty(entry2.getDisplayName())
    310                 && TextUtils.isEmpty(entry1.getDisplayName())) {
    311             return entry2;
    312         }
    313 
    314         // If only one has a display name that is not the same as the destination, use it
    315         if (!TextUtils.equals(entry1.getDisplayName(), entry1.getDestination())
    316                 && TextUtils.equals(entry2.getDisplayName(), entry2.getDestination())) {
    317             return entry1;
    318         }
    319 
    320         if (!TextUtils.equals(entry2.getDisplayName(), entry2.getDestination())
    321                 && TextUtils.equals(entry1.getDisplayName(), entry1.getDestination())) {
    322             return entry2;
    323         }
    324 
    325         // If only one has a photo, use it
    326         if ((entry1.getPhotoThumbnailUri() != null || entry1.getPhotoBytes() != null)
    327                 && (entry2.getPhotoThumbnailUri() == null && entry2.getPhotoBytes() == null)) {
    328             return entry1;
    329         }
    330 
    331         if ((entry2.getPhotoThumbnailUri() != null || entry2.getPhotoBytes() != null)
    332                 && (entry1.getPhotoThumbnailUri() == null && entry1.getPhotoBytes() == null)) {
    333             return entry2;
    334         }
    335 
    336         // Go with the second option as a default
    337         return entry2;
    338     }
    339 
    340     private static Cursor doQuery(CharSequence constraint, int limit, Long directoryId,
    341             Account account, ContentResolver resolver, Query query) {
    342         final Uri.Builder builder = query
    343                 .getContentFilterUri()
    344                 .buildUpon()
    345                 .appendPath(constraint.toString())
    346                 .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
    347                         String.valueOf(limit + BaseRecipientAdapter.ALLOWANCE_FOR_DUPLICATES));
    348         if (directoryId != null) {
    349             builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
    350                     String.valueOf(directoryId));
    351         }
    352         if (account != null) {
    353             builder.appendQueryParameter(BaseRecipientAdapter.PRIMARY_ACCOUNT_NAME, account.name);
    354             builder.appendQueryParameter(BaseRecipientAdapter.PRIMARY_ACCOUNT_TYPE, account.type);
    355         }
    356         final Cursor cursor = resolver.query(builder.build(), query.getProjection(), null, null,
    357                 null);
    358         return cursor;
    359     }
    360 
    361     public RecipientAlternatesAdapter(Context context, long contactId, Long directoryId,
    362             String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener,
    363             DropdownChipLayouter dropdownChipLayouter) {
    364         this(context, contactId, directoryId, lookupKey, currentId, queryMode, listener,
    365                 dropdownChipLayouter, null);
    366     }
    367 
    368     public RecipientAlternatesAdapter(Context context, long contactId, Long directoryId,
    369             String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener,
    370             DropdownChipLayouter dropdownChipLayouter, StateListDrawable deleteDrawable) {
    371         super(context,
    372                 getCursorForConstruction(context, contactId, directoryId, lookupKey, queryMode), 0);
    373         mCurrentId = currentId;
    374         mDirectoryId = directoryId;
    375         mCheckedItemChangedListener = listener;
    376 
    377         mDropdownChipLayouter = dropdownChipLayouter;
    378         mDeleteDrawable = deleteDrawable;
    379     }
    380 
    381     private static Cursor getCursorForConstruction(Context context, long contactId,
    382             Long directoryId, String lookupKey, int queryType) {
    383         final Cursor cursor;
    384         final String desiredMimeType;
    385         if (queryType == QUERY_TYPE_EMAIL) {
    386             final Uri uri;
    387             final StringBuilder selection = new StringBuilder();
    388             selection.append(Queries.EMAIL.getProjection()[Queries.Query.CONTACT_ID]);
    389             selection.append(" = ?");
    390 
    391             if (directoryId == null || lookupKey == null) {
    392                 uri = Queries.EMAIL.getContentUri();
    393                 desiredMimeType = null;
    394             } else {
    395                 final Uri.Builder builder = Contacts.getLookupUri(contactId, lookupKey).buildUpon();
    396                 builder.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
    397                         .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
    398                                 String.valueOf(directoryId));
    399                 uri = builder.build();
    400                 desiredMimeType = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
    401             }
    402             cursor = context.getContentResolver().query(
    403                     uri,
    404                     Queries.EMAIL.getProjection(),
    405                     selection.toString(), new String[] {
    406                         String.valueOf(contactId)
    407                     }, null);
    408         } else {
    409             final Uri uri;
    410             final StringBuilder selection = new StringBuilder();
    411             selection.append(Queries.PHONE.getProjection()[Queries.Query.CONTACT_ID]);
    412             selection.append(" = ?");
    413 
    414             if (lookupKey == null) {
    415                 uri = Queries.PHONE.getContentUri();
    416                 desiredMimeType = null;
    417             } else {
    418                 final Uri.Builder builder = Contacts.getLookupUri(contactId, lookupKey).buildUpon();
    419                 builder.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
    420                         .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
    421                                 String.valueOf(directoryId));
    422                 uri = builder.build();
    423                 desiredMimeType = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
    424             }
    425             cursor = context.getContentResolver().query(
    426                     uri,
    427                     Queries.PHONE.getProjection(),
    428                     selection.toString(), new String[] {
    429                         String.valueOf(contactId)
    430                     }, null);
    431         }
    432 
    433         final Cursor resultCursor = removeUndesiredDestinations(cursor, desiredMimeType, lookupKey);
    434         cursor.close();
    435 
    436         return resultCursor;
    437     }
    438 
    439     /**
    440      * @return a new cursor based on the given cursor with all duplicate destinations removed.
    441      *
    442      * It's only intended to use for the alternate list, so...
    443      * - This method ignores all other fields and dedupe solely on the destination.  Normally,
    444      * if a cursor contains multiple contacts and they have the same destination, we'd still want
    445      * to show both.
    446      * - This method creates a MatrixCursor, so all data will be kept in memory.  We wouldn't want
    447      * to do this if the original cursor is large, but it's okay here because the alternate list
    448      * won't be that big.
    449      *
    450      * @param desiredMimeType If this is non-<code>null</code>, only entries with this mime type
    451      *            will be added to the cursor
    452      * @param lookupKey The lookup key used for this contact if there isn't one in the cursor. This
    453      *            should be the same one used in the query that returned the cursor
    454      */
    455     // Visible for testing
    456     static Cursor removeUndesiredDestinations(final Cursor original, final String desiredMimeType,
    457             final String lookupKey) {
    458         final MatrixCursor result = new MatrixCursor(
    459                 original.getColumnNames(), original.getCount());
    460         final HashSet<String> destinationsSeen = new HashSet<String>();
    461 
    462         String defaultDisplayName = null;
    463         String defaultPhotoThumbnailUri = null;
    464         int defaultDisplayNameSource = 0;
    465 
    466         // Find some nice defaults in case we need them
    467         original.moveToPosition(-1);
    468         while (original.moveToNext()) {
    469             final String mimeType = original.getString(Query.MIME_TYPE);
    470 
    471             if (ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(
    472                     mimeType)) {
    473                 // Store this data
    474                 defaultDisplayName = original.getString(Query.NAME);
    475                 defaultPhotoThumbnailUri = original.getString(Query.PHOTO_THUMBNAIL_URI);
    476                 defaultDisplayNameSource = original.getInt(Query.DISPLAY_NAME_SOURCE);
    477                 break;
    478             }
    479         }
    480 
    481         original.moveToPosition(-1);
    482         while (original.moveToNext()) {
    483             if (desiredMimeType != null) {
    484                 final String mimeType = original.getString(Query.MIME_TYPE);
    485                 if (!desiredMimeType.equals(mimeType)) {
    486                     continue;
    487                 }
    488             }
    489             final String destination = original.getString(Query.DESTINATION);
    490             if (destinationsSeen.contains(destination)) {
    491                 continue;
    492             }
    493             destinationsSeen.add(destination);
    494 
    495             final Object[] row = new Object[] {
    496                     original.getString(Query.NAME),
    497                     original.getString(Query.DESTINATION),
    498                     original.getInt(Query.DESTINATION_TYPE),
    499                     original.getString(Query.DESTINATION_LABEL),
    500                     original.getLong(Query.CONTACT_ID),
    501                     original.getLong(Query.DATA_ID),
    502                     original.getString(Query.PHOTO_THUMBNAIL_URI),
    503                     original.getInt(Query.DISPLAY_NAME_SOURCE),
    504                     original.getString(Query.LOOKUP_KEY),
    505                     original.getString(Query.MIME_TYPE)
    506             };
    507 
    508             if (row[Query.NAME] == null) {
    509                 row[Query.NAME] = defaultDisplayName;
    510             }
    511             if (row[Query.PHOTO_THUMBNAIL_URI] == null) {
    512                 row[Query.PHOTO_THUMBNAIL_URI] = defaultPhotoThumbnailUri;
    513             }
    514             if ((Integer) row[Query.DISPLAY_NAME_SOURCE] == 0) {
    515                 row[Query.DISPLAY_NAME_SOURCE] = defaultDisplayNameSource;
    516             }
    517             if (row[Query.LOOKUP_KEY] == null) {
    518                 row[Query.LOOKUP_KEY] = lookupKey;
    519             }
    520 
    521             // Ensure we don't have two '?' like content://.../...?account_name=...?sz=...
    522             final String photoThumbnailUri = (String) row[Query.PHOTO_THUMBNAIL_URI];
    523             if (photoThumbnailUri != null) {
    524                 if (sCorrectedPhotoUris.containsKey(photoThumbnailUri)) {
    525                     row[Query.PHOTO_THUMBNAIL_URI] = sCorrectedPhotoUris.get(photoThumbnailUri);
    526                 } else if (photoThumbnailUri.indexOf('?') != photoThumbnailUri.lastIndexOf('?')) {
    527                     final String[] parts = photoThumbnailUri.split("\\?");
    528                     final StringBuilder correctedUriBuilder = new StringBuilder();
    529                     for (int i = 0; i < parts.length; i++) {
    530                         if (i == 1) {
    531                             correctedUriBuilder.append("?"); // We only want one of these
    532                         } else if (i > 1) {
    533                             correctedUriBuilder.append("&"); // And we want these elsewhere
    534                         }
    535                         correctedUriBuilder.append(parts[i]);
    536                     }
    537 
    538                     final String correctedUri = correctedUriBuilder.toString();
    539                     sCorrectedPhotoUris.put(photoThumbnailUri, correctedUri);
    540                     row[Query.PHOTO_THUMBNAIL_URI] = correctedUri;
    541                 }
    542             }
    543 
    544             result.addRow(row);
    545         }
    546 
    547         return result;
    548     }
    549 
    550     @Override
    551     public long getItemId(int position) {
    552         Cursor c = getCursor();
    553         if (c.moveToPosition(position)) {
    554             c.getLong(Queries.Query.DATA_ID);
    555         }
    556         return -1;
    557     }
    558 
    559     public RecipientEntry getRecipientEntry(int position) {
    560         Cursor c = getCursor();
    561         c.moveToPosition(position);
    562         return RecipientEntry.constructTopLevelEntry(
    563                 c.getString(Queries.Query.NAME),
    564                 c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
    565                 c.getString(Queries.Query.DESTINATION),
    566                 c.getInt(Queries.Query.DESTINATION_TYPE),
    567                 c.getString(Queries.Query.DESTINATION_LABEL),
    568                 c.getLong(Queries.Query.CONTACT_ID),
    569                 mDirectoryId,
    570                 c.getLong(Queries.Query.DATA_ID),
    571                 c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
    572                 true,
    573                 c.getString(Queries.Query.LOOKUP_KEY));
    574     }
    575 
    576     @Override
    577     public View getView(int position, View convertView, ViewGroup parent) {
    578         Cursor cursor = getCursor();
    579         cursor.moveToPosition(position);
    580         if (convertView == null) {
    581             convertView = mDropdownChipLayouter.newView(AdapterType.RECIPIENT_ALTERNATES);
    582         }
    583         if (cursor.getLong(Queries.Query.DATA_ID) == mCurrentId) {
    584             mCheckedItemPosition = position;
    585             if (mCheckedItemChangedListener != null) {
    586                 mCheckedItemChangedListener.onCheckedItemChanged(mCheckedItemPosition);
    587             }
    588         }
    589         bindView(convertView, convertView.getContext(), cursor);
    590         return convertView;
    591     }
    592 
    593     @Override
    594     public void bindView(View view, Context context, Cursor cursor) {
    595         int position = cursor.getPosition();
    596         RecipientEntry entry = getRecipientEntry(position);
    597 
    598         mDropdownChipLayouter.bindView(view, null, entry, position,
    599                 AdapterType.RECIPIENT_ALTERNATES, null, mDeleteDrawable);
    600     }
    601 
    602     @Override
    603     public View newView(Context context, Cursor cursor, ViewGroup parent) {
    604         return mDropdownChipLayouter.newView(AdapterType.RECIPIENT_ALTERNATES);
    605     }
    606 
    607     /*package*/ static interface OnCheckedItemChangedListener {
    608         public void onCheckedItemChanged(int position);
    609     }
    610 }
    611