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 { matchesFound(Map<String, RecipientEntry> results)72 public void matchesFound(Map<String, RecipientEntry> results); 73 /** 74 * Called with all addresses that could not be resolved to valid recipients. 75 */ matchesNotFound(Set<String> unfoundAddresses)76 public void matchesNotFound(Set<String> unfoundAddresses); 77 } 78 getMatchingRecipients(Context context, BaseRecipientAdapter adapter, ArrayList<String> inAddresses, Account account, RecipientMatchCallback callback)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 */ getMatchingRecipients(Context context, BaseRecipientAdapter adapter, ArrayList<String> inAddresses, int addressType, Account account, RecipientMatchCallback callback)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 getMatchingRecipientsFromDirectoryQueries(Context context, Map<String, RecipientEntry> recipientEntries, Set<String> addresses, Account account, Set<String> matchesNotFound, RecipientMatchCallback callback)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 getMatchingRecipientsFromDirectoryQueries(Context context, Map<String, RecipientEntry> recipientEntries, Set<String> addresses, Account account, Set<String> matchesNotFound, Queries.Query query, RecipientMatchCallback callback)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 getMatchingRecipientsFromExtensionMatcher(BaseRecipientAdapter adapter, Set<String> matchesNotFound, RecipientMatchCallback callback)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 processContactEntries(Cursor c, Long directoryId)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 */ getBetterRecipient(final RecipientEntry entry1, final RecipientEntry entry2)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 doQuery(CharSequence constraint, int limit, Long directoryId, Account account, ContentResolver resolver, Query query)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 RecipientAlternatesAdapter(Context context, long contactId, Long directoryId, String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener, DropdownChipLayouter dropdownChipLayouter)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 RecipientAlternatesAdapter(Context context, long contactId, Long directoryId, String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener, DropdownChipLayouter dropdownChipLayouter, StateListDrawable deleteDrawable)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 getCursorForConstruction(Context context, long contactId, Long directoryId, String lookupKey, int queryType)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 removeUndesiredDestinations(final Cursor original, final String desiredMimeType, final String lookupKey)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 getItemId(int position)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 getRecipientEntry(int position)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 getView(int position, View convertView, ViewGroup parent)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 bindView(View view, Context context, Cursor cursor)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 newView(Context context, Cursor cursor, ViewGroup parent)603 public View newView(Context context, Cursor cursor, ViewGroup parent) { 604 return mDropdownChipLayouter.newView(AdapterType.RECIPIENT_ALTERNATES); 605 } 606 607 /*package*/ static interface OnCheckedItemChangedListener { onCheckedItemChanged(int position)608 public void onCheckedItemChanged(int position); 609 } 610 } 611