blob: 10502b19f49984bffc9da6ce033f5d896afe7300 [file] [log] [blame]
Mindy Pereira84dd9a82011-06-01 11:13:08 -07001/*
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 */
Mindy Pereira97d77682011-06-01 10:48:55 -070016
17package com.android.ex.chips;
18
mindyp16923ee2012-12-10 11:58:29 -080019import android.accounts.Account;
20import android.content.ContentResolver;
Mindy Pereira97d77682011-06-01 10:48:55 -070021import android.content.Context;
22import android.database.Cursor;
Makoto Onukia5d37c82012-05-03 13:40:45 -070023import android.database.MatrixCursor;
mindyp16923ee2012-12-10 11:58:29 -080024import android.net.Uri;
25import android.provider.ContactsContract;
Scott Kennedy7a4e6772013-11-21 14:31:33 -080026import android.provider.ContactsContract.Contacts;
Scott Kennedyef3f76c2013-02-25 10:48:25 -080027import android.text.TextUtils;
Mindy Pereira18529312011-06-28 11:00:52 -070028import android.text.util.Rfc822Token;
29import android.text.util.Rfc822Tokenizer;
Mindy Pereira18987c42011-07-11 11:26:46 -070030import android.util.Log;
Mindy Pereira97d77682011-06-01 10:48:55 -070031import android.view.View;
32import android.view.ViewGroup;
33import android.widget.CursorAdapter;
Mindy Pereira97d77682011-06-01 10:48:55 -070034
mindyp16923ee2012-12-10 11:58:29 -080035import com.android.ex.chips.BaseRecipientAdapter.DirectoryListQuery;
36import com.android.ex.chips.BaseRecipientAdapter.DirectorySearchParams;
Kevin Linb10d1c62014-01-24 12:45:00 -080037import com.android.ex.chips.DropdownChipLayouter.AdapterType;
Tom Taylor80f4abf2012-04-06 13:37:20 -070038import com.android.ex.chips.Queries.Query;
Makoto Onukia5d37c82012-05-03 13:40:45 -070039
Mindy Pereira03cfe3e2012-05-24 13:21:59 -070040import java.util.ArrayList;
Mindy Pereira18529312011-06-28 11:00:52 -070041import java.util.HashMap;
Makoto Onukia5d37c82012-05-03 13:40:45 -070042import java.util.HashSet;
mindyp16923ee2012-12-10 11:58:29 -080043import java.util.List;
Scott Kennedy94fa3012013-02-15 18:22:05 -080044import java.util.Map;
45import java.util.Set;
Mindy Pereira18529312011-06-28 11:00:52 -070046
Tom Taylor80f4abf2012-04-06 13:37:20 -070047/**
48 * RecipientAlternatesAdapter backs the RecipientEditTextView for managing contacts
49 * queried by email or by phone number.
50 */
Mindy Pereira97d77682011-06-01 10:48:55 -070051public class RecipientAlternatesAdapter extends CursorAdapter {
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -070052 public static final int MAX_LOOKUPS = 50;
Mindy Pereira97d77682011-06-01 10:48:55 -070053
Mindy Pereira97d77682011-06-01 10:48:55 -070054 private final long mCurrentId;
55
Mindy Pereira007a76b2011-06-14 11:39:36 -070056 private int mCheckedItemPosition = -1;
57
Mindy Pereira50863912011-06-16 18:54:10 -070058 private OnCheckedItemChangedListener mCheckedItemChangedListener;
59
Mindy Pereira18987c42011-07-11 11:26:46 -070060 private static final String TAG = "RecipAlternates";
61
Tom Taylor80f4abf2012-04-06 13:37:20 -070062 public static final int QUERY_TYPE_EMAIL = 0;
63 public static final int QUERY_TYPE_PHONE = 1;
Scott Kennedy7a4e6772013-11-21 14:31:33 -080064 private final Long mDirectoryId;
Kevin Linb10d1c62014-01-24 12:45:00 -080065 private DropdownChipLayouter mDropdownChipLayouter;
Tom Taylor80f4abf2012-04-06 13:37:20 -070066
Scott Kennedy7a4e6772013-11-21 14:31:33 -080067 private static final Map<String, String> sCorrectedPhotoUris = new HashMap<String, String>();
68
mindyp16923ee2012-12-10 11:58:29 -080069 public interface RecipientMatchCallback {
Scott Kennedy94fa3012013-02-15 18:22:05 -080070 public void matchesFound(Map<String, RecipientEntry> results);
71 /**
72 * Called with all addresses that could not be resolved to valid recipients.
73 */
Scott Kennedyf7e202d2013-03-06 21:38:10 -080074 public void matchesNotFound(Set<String> unfoundAddresses);
mindyp16923ee2012-12-10 11:58:29 -080075 }
76
Alon Albert76f1f2d2013-07-14 15:32:49 +030077 public static void getMatchingRecipients(Context context, BaseRecipientAdapter adapter,
78 ArrayList<String> inAddresses, Account account, RecipientMatchCallback callback) {
79 getMatchingRecipients(context, adapter, inAddresses, QUERY_TYPE_EMAIL, account, callback);
Tom Taylor80f4abf2012-04-06 13:37:20 -070080 }
81
Mindy Pereira18529312011-06-28 11:00:52 -070082 /**
83 * Get a HashMap of address to RecipientEntry that contains all contact
84 * information for a contact with the provided address, if one exists. This
85 * may block the UI, so run it in an async task.
86 *
87 * @param context Context.
Daisuke Miyakawa72117472011-07-20 14:10:10 -070088 * @param inAddresses Array of addresses on which to perform the lookup.
mindyp16923ee2012-12-10 11:58:29 -080089 * @param callback RecipientMatchCallback called when a match or matches are found.
Mindy Pereira18529312011-06-28 11:00:52 -070090 */
Alon Albert76f1f2d2013-07-14 15:32:49 +030091 public static void getMatchingRecipients(Context context, BaseRecipientAdapter adapter,
92 ArrayList<String> inAddresses, int addressType, Account account,
93 RecipientMatchCallback callback) {
Tom Taylor80f4abf2012-04-06 13:37:20 -070094 Queries.Query query;
95 if (addressType == QUERY_TYPE_EMAIL) {
96 query = Queries.EMAIL;
97 } else {
98 query = Queries.PHONE;
99 }
Mindy Pereira03cfe3e2012-05-24 13:21:59 -0700100 int addressesSize = Math.min(MAX_LOOKUPS, inAddresses.size());
mindyp16923ee2012-12-10 11:58:29 -0800101 HashSet<String> addresses = new HashSet<String>();
Mindy Pereira18529312011-06-28 11:00:52 -0700102 StringBuilder bindString = new StringBuilder();
103 // Create the "?" string and set up arguments.
104 for (int i = 0; i < addressesSize; i++) {
Mindy Pereira03cfe3e2012-05-24 13:21:59 -0700105 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses.get(i).toLowerCase());
mindyp16923ee2012-12-10 11:58:29 -0800106 addresses.add(tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i));
Mindy Pereira18529312011-06-28 11:00:52 -0700107 bindString.append("?");
108 if (i < addressesSize - 1) {
109 bindString.append(",");
110 }
111 }
112
Mindy Pereira18987c42011-07-11 11:26:46 -0700113 if (Log.isLoggable(TAG, Log.DEBUG)) {
114 Log.d(TAG, "Doing reverse lookup for " + addresses.toString());
115 }
116
mindyp16923ee2012-12-10 11:58:29 -0800117 String[] addressArray = new String[addresses.size()];
118 addresses.toArray(addressArray);
119 HashMap<String, RecipientEntry> recipientEntries = null;
120 Cursor c = null;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700121
mindyp16923ee2012-12-10 11:58:29 -0800122 try {
123 c = context.getContentResolver().query(
124 query.getContentUri(),
125 query.getProjection(),
126 query.getProjection()[Queries.Query.DESTINATION] + " IN ("
127 + bindString.toString() + ")", addressArray, null);
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800128 recipientEntries = processContactEntries(c, null /* directoryId */);
mindyp16923ee2012-12-10 11:58:29 -0800129 callback.matchesFound(recipientEntries);
130 } finally {
131 if (c != null) {
Mindy Pereira18529312011-06-28 11:00:52 -0700132 c.close();
133 }
134 }
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700135
136 final Set<String> matchesNotFound = new HashSet<String>();
137
138 getMatchingRecipientsFromDirectoryQueries(context, recipientEntries,
139 addresses, account, matchesNotFound, query, callback);
140
141 getMatchingRecipientsFromExtensionMatcher(adapter, matchesNotFound, callback);
142 }
143
144 public static void getMatchingRecipientsFromDirectoryQueries(Context context,
145 Map<String, RecipientEntry> recipientEntries, Set<String> addresses,
146 Account account, Set<String> matchesNotFound,
147 RecipientMatchCallback callback) {
148 getMatchingRecipientsFromDirectoryQueries(
149 context, recipientEntries, addresses, account,
150 matchesNotFound, Queries.EMAIL, callback);
151 }
152
153 private static void getMatchingRecipientsFromDirectoryQueries(Context context,
154 Map<String, RecipientEntry> recipientEntries, Set<String> addresses,
155 Account account, Set<String> matchesNotFound, Queries.Query query,
156 RecipientMatchCallback callback) {
mindyp16923ee2012-12-10 11:58:29 -0800157 // See if any entries did not resolve; if so, we need to check other
158 // directories
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700159
mindyp16923ee2012-12-10 11:58:29 -0800160 if (recipientEntries.size() < addresses.size()) {
161 final List<DirectorySearchParams> paramsList;
Scott Kennedy539c2b12013-03-13 19:25:12 -0700162 Cursor directoryCursor = null;
163 try {
164 directoryCursor = context.getContentResolver().query(DirectoryListQuery.URI,
165 DirectoryListQuery.PROJECTION, null, null, null);
Scott Kennedy771e4172013-06-07 08:48:23 -0700166 if (directoryCursor == null) {
167 paramsList = null;
168 } else {
169 paramsList = BaseRecipientAdapter.setupOtherDirectories(context,
170 directoryCursor, account);
171 }
Scott Kennedy539c2b12013-03-13 19:25:12 -0700172 } finally {
173 if (directoryCursor != null) {
174 directoryCursor.close();
175 }
176 }
mindyp16923ee2012-12-10 11:58:29 -0800177 // Run a directory query for each unmatched recipient.
178 HashSet<String> unresolvedAddresses = new HashSet<String>();
179 for (String address : addresses) {
180 if (!recipientEntries.containsKey(address)) {
181 unresolvedAddresses.add(address);
182 }
183 }
Scott Kennedy94fa3012013-02-15 18:22:05 -0800184
185 matchesNotFound.addAll(unresolvedAddresses);
186
Scott Kennedy771e4172013-06-07 08:48:23 -0700187 if (paramsList != null) {
188 Cursor directoryContactsCursor = null;
189 for (String unresolvedAddress : unresolvedAddresses) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800190 Long directoryId = null;
Scott Kennedy771e4172013-06-07 08:48:23 -0700191 for (int i = 0; i < paramsList.size(); i++) {
192 try {
193 directoryContactsCursor = doQuery(unresolvedAddress, 1,
194 paramsList.get(i).directoryId, account,
195 context.getContentResolver(), query);
196 } finally {
197 if (directoryContactsCursor != null
198 && directoryContactsCursor.getCount() == 0) {
199 directoryContactsCursor.close();
200 directoryContactsCursor = null;
201 } else {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800202 directoryId = paramsList.get(i).directoryId;
Scott Kennedy771e4172013-06-07 08:48:23 -0700203 break;
204 }
mindyp16923ee2012-12-10 11:58:29 -0800205 }
206 }
Scott Kennedy771e4172013-06-07 08:48:23 -0700207 if (directoryContactsCursor != null) {
208 try {
209 final Map<String, RecipientEntry> entries =
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800210 processContactEntries(directoryContactsCursor, directoryId);
Scott Kennedy94fa3012013-02-15 18:22:05 -0800211
Scott Kennedy771e4172013-06-07 08:48:23 -0700212 for (final String address : entries.keySet()) {
213 matchesNotFound.remove(address);
214 }
215
216 callback.matchesFound(entries);
217 } finally {
218 directoryContactsCursor.close();
Scott Kennedy94fa3012013-02-15 18:22:05 -0800219 }
mindyp16923ee2012-12-10 11:58:29 -0800220 }
221 }
222 }
223 }
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700224 }
Scott Kennedy94fa3012013-02-15 18:22:05 -0800225
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700226 public static void getMatchingRecipientsFromExtensionMatcher(BaseRecipientAdapter adapter,
227 Set<String> matchesNotFound, RecipientMatchCallback callback) {
Alon Albert76f1f2d2013-07-14 15:32:49 +0300228 // If no matches found in contact provider or the directories, try the extension
229 // matcher.
230 // todo (aalbert): This whole method needs to be in the adapter?
231 if (adapter != null) {
232 final Map<String, RecipientEntry> entries =
233 adapter.getMatchingRecipients(matchesNotFound);
234 if (entries != null && entries.size() > 0) {
235 callback.matchesFound(entries);
236 for (final String address : entries.keySet()) {
237 matchesNotFound.remove(address);
238 }
239 }
240 }
Scott Kennedy94fa3012013-02-15 18:22:05 -0800241 callback.matchesNotFound(matchesNotFound);
mindyp16923ee2012-12-10 11:58:29 -0800242 }
243
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800244 private static HashMap<String, RecipientEntry> processContactEntries(Cursor c,
245 Long directoryId) {
mindyp16923ee2012-12-10 11:58:29 -0800246 HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>();
247 if (c != null && c.moveToFirst()) {
248 do {
249 String address = c.getString(Queries.Query.DESTINATION);
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800250
251 final RecipientEntry newRecipientEntry = RecipientEntry.constructTopLevelEntry(
mindyp16923ee2012-12-10 11:58:29 -0800252 c.getString(Queries.Query.NAME),
253 c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
254 c.getString(Queries.Query.DESTINATION),
255 c.getInt(Queries.Query.DESTINATION_TYPE),
256 c.getString(Queries.Query.DESTINATION_LABEL),
257 c.getLong(Queries.Query.CONTACT_ID),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800258 directoryId,
mindyp16923ee2012-12-10 11:58:29 -0800259 c.getLong(Queries.Query.DATA_ID),
260 c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
Scott Kennedy514f8a72013-11-13 15:58:08 -0800261 true,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800262 c.getString(Queries.Query.LOOKUP_KEY));
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800263
264 /*
265 * In certain situations, we may have two results for one address, where one of the
266 * results is just the email address, and the other has a name and photo, so we want
267 * to use the better one.
268 */
269 final RecipientEntry recipientEntry =
270 getBetterRecipient(recipientEntries.get(address), newRecipientEntry);
271
272 recipientEntries.put(address, recipientEntry);
mindyp16923ee2012-12-10 11:58:29 -0800273 if (Log.isLoggable(TAG, Log.DEBUG)) {
274 Log.d(TAG, "Received reverse look up information for " + address
275 + " RESULTS: "
276 + " NAME : " + c.getString(Queries.Query.NAME)
277 + " CONTACT ID : " + c.getLong(Queries.Query.CONTACT_ID)
278 + " ADDRESS :" + c.getString(Queries.Query.DESTINATION));
279 }
280 } while (c.moveToNext());
281 }
Mindy Pereira18529312011-06-28 11:00:52 -0700282 return recipientEntries;
283 }
284
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800285 /**
286 * Given two {@link RecipientEntry}s for the same email address, this will return the one that
287 * contains more complete information for display purposes. Defaults to <code>entry2</code> if
288 * no significant differences are found.
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800289 */
Scott Kennedy78f38a02013-02-27 11:21:40 -0800290 static RecipientEntry getBetterRecipient(final RecipientEntry entry1,
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800291 final RecipientEntry entry2) {
292 // If only one has passed in, use it
293 if (entry2 == null) {
294 return entry1;
295 }
296
297 if (entry1 == null) {
298 return entry2;
299 }
300
301 // If only one has a display name, use it
302 if (!TextUtils.isEmpty(entry1.getDisplayName())
303 && TextUtils.isEmpty(entry2.getDisplayName())) {
304 return entry1;
305 }
306
307 if (!TextUtils.isEmpty(entry2.getDisplayName())
308 && TextUtils.isEmpty(entry1.getDisplayName())) {
309 return entry2;
310 }
311
312 // If only one has a display name that is not the same as the destination, use it
313 if (!TextUtils.equals(entry1.getDisplayName(), entry1.getDestination())
314 && TextUtils.equals(entry2.getDisplayName(), entry2.getDestination())) {
315 return entry1;
316 }
317
318 if (!TextUtils.equals(entry2.getDisplayName(), entry2.getDestination())
319 && TextUtils.equals(entry1.getDisplayName(), entry1.getDestination())) {
320 return entry2;
321 }
322
323 // If only one has a photo, use it
324 if ((entry1.getPhotoThumbnailUri() != null || entry1.getPhotoBytes() != null)
325 && (entry2.getPhotoThumbnailUri() == null && entry2.getPhotoBytes() == null)) {
326 return entry1;
327 }
328
329 if ((entry2.getPhotoThumbnailUri() != null || entry2.getPhotoBytes() != null)
330 && (entry1.getPhotoThumbnailUri() == null && entry1.getPhotoBytes() == null)) {
331 return entry2;
332 }
333
334 // Go with the second option as a default
335 return entry2;
336 }
337
mindyp16923ee2012-12-10 11:58:29 -0800338 private static Cursor doQuery(CharSequence constraint, int limit, Long directoryId,
339 Account account, ContentResolver resolver, Query query) {
340 final Uri.Builder builder = query
341 .getContentFilterUri()
342 .buildUpon()
343 .appendPath(constraint.toString())
344 .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
345 String.valueOf(limit + BaseRecipientAdapter.ALLOWANCE_FOR_DUPLICATES));
346 if (directoryId != null) {
347 builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
348 String.valueOf(directoryId));
349 }
350 if (account != null) {
351 builder.appendQueryParameter(BaseRecipientAdapter.PRIMARY_ACCOUNT_NAME, account.name);
352 builder.appendQueryParameter(BaseRecipientAdapter.PRIMARY_ACCOUNT_TYPE, account.type);
353 }
354 final Cursor cursor = resolver.query(builder.build(), query.getProjection(), null, null,
355 null);
356 return cursor;
357 }
358
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800359 public RecipientAlternatesAdapter(Context context, long contactId, Long directoryId,
360 String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener,
361 DropdownChipLayouter dropdownChipLayouter) {
362 super(context,
363 getCursorForConstruction(context, contactId, directoryId, lookupKey, queryMode), 0);
Mindy Pereira97d77682011-06-01 10:48:55 -0700364 mCurrentId = currentId;
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800365 mDirectoryId = directoryId;
Mindy Pereira50863912011-06-16 18:54:10 -0700366 mCheckedItemChangedListener = listener;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700367
Kevin Linb10d1c62014-01-24 12:45:00 -0800368 mDropdownChipLayouter = dropdownChipLayouter;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700369 }
370
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800371 private static Cursor getCursorForConstruction(Context context, long contactId,
372 Long directoryId, String lookupKey, int queryType) {
Makoto Onukia5d37c82012-05-03 13:40:45 -0700373 final Cursor cursor;
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800374 final String desiredMimeType;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700375 if (queryType == QUERY_TYPE_EMAIL) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800376 final Uri uri;
377 final StringBuilder selection = new StringBuilder();
378 selection.append(Queries.EMAIL.getProjection()[Queries.Query.CONTACT_ID]);
379 selection.append(" = ?");
380
381 if (directoryId == null || lookupKey == null) {
382 uri = Queries.EMAIL.getContentUri();
383 desiredMimeType = null;
384 } else {
385 final Uri.Builder builder = Contacts.getLookupUri(contactId, lookupKey).buildUpon();
386 builder.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
387 .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
388 String.valueOf(directoryId));
389 uri = builder.build();
390 desiredMimeType = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
391 }
Makoto Onukia5d37c82012-05-03 13:40:45 -0700392 cursor = context.getContentResolver().query(
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800393 uri,
Tom Taylor80f4abf2012-04-06 13:37:20 -0700394 Queries.EMAIL.getProjection(),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800395 selection.toString(), new String[] {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700396 String.valueOf(contactId)
397 }, null);
398 } else {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800399 final Uri uri;
400 final StringBuilder selection = new StringBuilder();
401 selection.append(Queries.PHONE.getProjection()[Queries.Query.CONTACT_ID]);
402 selection.append(" = ?");
403
404 if (lookupKey == null) {
405 uri = Queries.PHONE.getContentUri();
406 desiredMimeType = null;
407 } else {
408 final Uri.Builder builder = Contacts.getLookupUri(contactId, lookupKey).buildUpon();
409 builder.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
410 .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
411 String.valueOf(directoryId));
412 uri = builder.build();
413 desiredMimeType = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
414 }
Makoto Onukia5d37c82012-05-03 13:40:45 -0700415 cursor = context.getContentResolver().query(
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800416 uri,
Tom Taylor80f4abf2012-04-06 13:37:20 -0700417 Queries.PHONE.getProjection(),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800418 selection.toString(), new String[] {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700419 String.valueOf(contactId)
420 }, null);
421 }
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800422
423 final Cursor resultCursor = removeUndesiredDestinations(cursor, desiredMimeType, lookupKey);
424 cursor.close();
425
426 return resultCursor;
Makoto Onukia5d37c82012-05-03 13:40:45 -0700427 }
428
429 /**
430 * @return a new cursor based on the given cursor with all duplicate destinations removed.
431 *
432 * It's only intended to use for the alternate list, so...
433 * - This method ignores all other fields and dedupe solely on the destination. Normally,
434 * if a cursor contains multiple contacts and they have the same destination, we'd still want
435 * to show both.
436 * - This method creates a MatrixCursor, so all data will be kept in memory. We wouldn't want
437 * to do this if the original cursor is large, but it's okay here because the alternate list
438 * won't be that big.
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800439 *
440 * @param desiredMimeType If this is non-<code>null</code>, only entries with this mime type
441 * will be added to the cursor
442 * @param lookupKey The lookup key used for this contact if there isn't one in the cursor. This
443 * should be the same one used in the query that returned the cursor
Makoto Onukia5d37c82012-05-03 13:40:45 -0700444 */
445 // Visible for testing
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800446 static Cursor removeUndesiredDestinations(final Cursor original, final String desiredMimeType,
447 final String lookupKey) {
Makoto Onukia5d37c82012-05-03 13:40:45 -0700448 final MatrixCursor result = new MatrixCursor(
449 original.getColumnNames(), original.getCount());
450 final HashSet<String> destinationsSeen = new HashSet<String>();
451
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800452 String defaultDisplayName = null;
453 String defaultPhotoThumbnailUri = null;
454 int defaultDisplayNameSource = 0;
455
456 // Find some nice defaults in case we need them
Makoto Onukia5d37c82012-05-03 13:40:45 -0700457 original.moveToPosition(-1);
458 while (original.moveToNext()) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800459 final String mimeType = original.getString(Query.MIME_TYPE);
460
461 if (ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(
462 mimeType)) {
463 // Store this data
464 defaultDisplayName = original.getString(Query.NAME);
465 defaultPhotoThumbnailUri = original.getString(Query.PHOTO_THUMBNAIL_URI);
466 defaultDisplayNameSource = original.getInt(Query.DISPLAY_NAME_SOURCE);
467 break;
468 }
469 }
470
471 original.moveToPosition(-1);
472 while (original.moveToNext()) {
473 if (desiredMimeType != null) {
474 final String mimeType = original.getString(Query.MIME_TYPE);
475 if (!desiredMimeType.equals(mimeType)) {
476 continue;
477 }
478 }
Makoto Onukia5d37c82012-05-03 13:40:45 -0700479 final String destination = original.getString(Query.DESTINATION);
480 if (destinationsSeen.contains(destination)) {
481 continue;
482 }
483 destinationsSeen.add(destination);
484
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800485 final Object[] row = new Object[] {
Makoto Onukia5d37c82012-05-03 13:40:45 -0700486 original.getString(Query.NAME),
487 original.getString(Query.DESTINATION),
488 original.getInt(Query.DESTINATION_TYPE),
489 original.getString(Query.DESTINATION_LABEL),
490 original.getLong(Query.CONTACT_ID),
491 original.getLong(Query.DATA_ID),
492 original.getString(Query.PHOTO_THUMBNAIL_URI),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800493 original.getInt(Query.DISPLAY_NAME_SOURCE),
494 original.getString(Query.LOOKUP_KEY),
495 original.getString(Query.MIME_TYPE)
496 };
497
498 if (row[Query.NAME] == null) {
499 row[Query.NAME] = defaultDisplayName;
500 }
501 if (row[Query.PHOTO_THUMBNAIL_URI] == null) {
502 row[Query.PHOTO_THUMBNAIL_URI] = defaultPhotoThumbnailUri;
503 }
504 if ((Integer) row[Query.DISPLAY_NAME_SOURCE] == 0) {
505 row[Query.DISPLAY_NAME_SOURCE] = defaultDisplayNameSource;
506 }
507 if (row[Query.LOOKUP_KEY] == null) {
508 row[Query.LOOKUP_KEY] = lookupKey;
509 }
510
511 // Ensure we don't have two '?' like content://.../...?account_name=...?sz=...
512 final String photoThumbnailUri = (String) row[Query.PHOTO_THUMBNAIL_URI];
513 if (photoThumbnailUri != null) {
514 if (sCorrectedPhotoUris.containsKey(photoThumbnailUri)) {
515 row[Query.PHOTO_THUMBNAIL_URI] = sCorrectedPhotoUris.get(photoThumbnailUri);
516 } else if (photoThumbnailUri.indexOf('?') != photoThumbnailUri.lastIndexOf('?')) {
517 final String[] parts = photoThumbnailUri.split("\\?");
518 final StringBuilder correctedUriBuilder = new StringBuilder();
519 for (int i = 0; i < parts.length; i++) {
520 if (i == 1) {
521 correctedUriBuilder.append("?"); // We only want one of these
522 } else if (i > 1) {
523 correctedUriBuilder.append("&"); // And we want these elsewhere
524 }
525 correctedUriBuilder.append(parts[i]);
526 }
527
528 final String correctedUri = correctedUriBuilder.toString();
529 sCorrectedPhotoUris.put(photoThumbnailUri, correctedUri);
530 row[Query.PHOTO_THUMBNAIL_URI] = correctedUri;
531 }
532 }
533
534 result.addRow(row);
Makoto Onukia5d37c82012-05-03 13:40:45 -0700535 }
536
537 return result;
Mindy Pereira97d77682011-06-01 10:48:55 -0700538 }
539
540 @Override
541 public long getItemId(int position) {
542 Cursor c = getCursor();
Mindy Pereira3bb52162011-07-01 14:52:22 -0700543 if (c.moveToPosition(position)) {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700544 c.getLong(Queries.Query.DATA_ID);
Mindy Pereira3bb52162011-07-01 14:52:22 -0700545 }
546 return -1;
Mindy Pereira97d77682011-06-01 10:48:55 -0700547 }
548
549 public RecipientEntry getRecipientEntry(int position) {
550 Cursor c = getCursor();
551 c.moveToPosition(position);
Tom Taylor80f4abf2012-04-06 13:37:20 -0700552 return RecipientEntry.constructTopLevelEntry(
553 c.getString(Queries.Query.NAME),
Makoto Onuki00adb322012-05-01 16:50:41 -0700554 c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
Tom Taylor80f4abf2012-04-06 13:37:20 -0700555 c.getString(Queries.Query.DESTINATION),
556 c.getInt(Queries.Query.DESTINATION_TYPE),
557 c.getString(Queries.Query.DESTINATION_LABEL),
558 c.getLong(Queries.Query.CONTACT_ID),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800559 mDirectoryId,
Tom Taylor80f4abf2012-04-06 13:37:20 -0700560 c.getLong(Queries.Query.DATA_ID),
mindypccb8e232012-11-14 11:59:27 -0800561 c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
Scott Kennedy514f8a72013-11-13 15:58:08 -0800562 true,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800563 c.getString(Queries.Query.LOOKUP_KEY));
Mindy Pereira97d77682011-06-01 10:48:55 -0700564 }
565
566 @Override
567 public View getView(int position, View convertView, ViewGroup parent) {
Mindy Pereira67ad4892011-06-02 12:21:22 -0700568 Cursor cursor = getCursor();
569 cursor.moveToPosition(position);
Mindy Pereira97d77682011-06-01 10:48:55 -0700570 if (convertView == null) {
Jin Cao4ddcdae2014-07-28 19:03:56 -0700571 convertView = mDropdownChipLayouter.newView(AdapterType.RECIPIENT_ALTERNATES);
Mindy Pereira007a76b2011-06-14 11:39:36 -0700572 }
Tom Taylor80f4abf2012-04-06 13:37:20 -0700573 if (cursor.getLong(Queries.Query.DATA_ID) == mCurrentId) {
Mindy Pereira007a76b2011-06-14 11:39:36 -0700574 mCheckedItemPosition = position;
Mindy Pereira50863912011-06-16 18:54:10 -0700575 if (mCheckedItemChangedListener != null) {
576 mCheckedItemChangedListener.onCheckedItemChanged(mCheckedItemPosition);
577 }
Mindy Pereira97d77682011-06-01 10:48:55 -0700578 }
Mindy Pereira67ad4892011-06-02 12:21:22 -0700579 bindView(convertView, convertView.getContext(), cursor);
Mindy Pereira97d77682011-06-01 10:48:55 -0700580 return convertView;
581 }
582
583 @Override
584 public void bindView(View view, Context context, Cursor cursor) {
Mindy Pereira67ad4892011-06-02 12:21:22 -0700585 int position = cursor.getPosition();
Mindy Pereira67ad4892011-06-02 12:21:22 -0700586 RecipientEntry entry = getRecipientEntry(position);
Daisuke Miyakawa102cf102011-07-21 08:48:27 -0700587
Kevin Linb10d1c62014-01-24 12:45:00 -0800588 mDropdownChipLayouter.bindView(view, null, entry, position,
589 AdapterType.RECIPIENT_ALTERNATES, null);
Mindy Pereira97d77682011-06-01 10:48:55 -0700590 }
591
592 @Override
593 public View newView(Context context, Cursor cursor, ViewGroup parent) {
Jin Cao4ddcdae2014-07-28 19:03:56 -0700594 return mDropdownChipLayouter.newView(AdapterType.RECIPIENT_ALTERNATES);
Mindy Pereira007a76b2011-06-14 11:39:36 -0700595 }
596
Mindy Pereira50863912011-06-16 18:54:10 -0700597 /*package*/ static interface OnCheckedItemChangedListener {
598 public void onCheckedItemChanged(int position);
Mindy Pereira97d77682011-06-01 10:48:55 -0700599 }
600}