blob: f6f662d2181845c2bf279a46cf96eb7fc4e4fd68 [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 {
Mindy Pereira18529312011-06-28 11:00:52 -070052 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 * @return HashMap<String,RecipientEntry>
91 */
Alon Albert76f1f2d2013-07-14 15:32:49 +030092 public static void getMatchingRecipients(Context context, BaseRecipientAdapter adapter,
93 ArrayList<String> inAddresses, int addressType, Account account,
94 RecipientMatchCallback callback) {
Tom Taylor80f4abf2012-04-06 13:37:20 -070095 Queries.Query query;
96 if (addressType == QUERY_TYPE_EMAIL) {
97 query = Queries.EMAIL;
98 } else {
99 query = Queries.PHONE;
100 }
Mindy Pereira03cfe3e2012-05-24 13:21:59 -0700101 int addressesSize = Math.min(MAX_LOOKUPS, inAddresses.size());
mindyp16923ee2012-12-10 11:58:29 -0800102 HashSet<String> addresses = new HashSet<String>();
Mindy Pereira18529312011-06-28 11:00:52 -0700103 StringBuilder bindString = new StringBuilder();
104 // Create the "?" string and set up arguments.
105 for (int i = 0; i < addressesSize; i++) {
Mindy Pereira03cfe3e2012-05-24 13:21:59 -0700106 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses.get(i).toLowerCase());
mindyp16923ee2012-12-10 11:58:29 -0800107 addresses.add(tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i));
Mindy Pereira18529312011-06-28 11:00:52 -0700108 bindString.append("?");
109 if (i < addressesSize - 1) {
110 bindString.append(",");
111 }
112 }
113
Mindy Pereira18987c42011-07-11 11:26:46 -0700114 if (Log.isLoggable(TAG, Log.DEBUG)) {
115 Log.d(TAG, "Doing reverse lookup for " + addresses.toString());
116 }
117
mindyp16923ee2012-12-10 11:58:29 -0800118 String[] addressArray = new String[addresses.size()];
119 addresses.toArray(addressArray);
120 HashMap<String, RecipientEntry> recipientEntries = null;
121 Cursor c = null;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700122
mindyp16923ee2012-12-10 11:58:29 -0800123 try {
124 c = context.getContentResolver().query(
125 query.getContentUri(),
126 query.getProjection(),
127 query.getProjection()[Queries.Query.DESTINATION] + " IN ("
128 + bindString.toString() + ")", addressArray, null);
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800129 recipientEntries = processContactEntries(c, null /* directoryId */);
mindyp16923ee2012-12-10 11:58:29 -0800130 callback.matchesFound(recipientEntries);
131 } finally {
132 if (c != null) {
Mindy Pereira18529312011-06-28 11:00:52 -0700133 c.close();
134 }
135 }
mindyp16923ee2012-12-10 11:58:29 -0800136 // See if any entries did not resolve; if so, we need to check other
137 // directories
Scott Kennedy94fa3012013-02-15 18:22:05 -0800138 final Set<String> matchesNotFound = new HashSet<String>();
mindyp16923ee2012-12-10 11:58:29 -0800139 if (recipientEntries.size() < addresses.size()) {
140 final List<DirectorySearchParams> paramsList;
Scott Kennedy539c2b12013-03-13 19:25:12 -0700141 Cursor directoryCursor = null;
142 try {
143 directoryCursor = context.getContentResolver().query(DirectoryListQuery.URI,
144 DirectoryListQuery.PROJECTION, null, null, null);
Scott Kennedy771e4172013-06-07 08:48:23 -0700145 if (directoryCursor == null) {
146 paramsList = null;
147 } else {
148 paramsList = BaseRecipientAdapter.setupOtherDirectories(context,
149 directoryCursor, account);
150 }
Scott Kennedy539c2b12013-03-13 19:25:12 -0700151 } finally {
152 if (directoryCursor != null) {
153 directoryCursor.close();
154 }
155 }
mindyp16923ee2012-12-10 11:58:29 -0800156 // Run a directory query for each unmatched recipient.
157 HashSet<String> unresolvedAddresses = new HashSet<String>();
158 for (String address : addresses) {
159 if (!recipientEntries.containsKey(address)) {
160 unresolvedAddresses.add(address);
161 }
162 }
Scott Kennedy94fa3012013-02-15 18:22:05 -0800163
164 matchesNotFound.addAll(unresolvedAddresses);
165
Scott Kennedy771e4172013-06-07 08:48:23 -0700166 if (paramsList != null) {
167 Cursor directoryContactsCursor = null;
168 for (String unresolvedAddress : unresolvedAddresses) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800169 Long directoryId = null;
Scott Kennedy771e4172013-06-07 08:48:23 -0700170 for (int i = 0; i < paramsList.size(); i++) {
171 try {
172 directoryContactsCursor = doQuery(unresolvedAddress, 1,
173 paramsList.get(i).directoryId, account,
174 context.getContentResolver(), query);
175 } finally {
176 if (directoryContactsCursor != null
177 && directoryContactsCursor.getCount() == 0) {
178 directoryContactsCursor.close();
179 directoryContactsCursor = null;
180 } else {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800181 directoryId = paramsList.get(i).directoryId;
Scott Kennedy771e4172013-06-07 08:48:23 -0700182 break;
183 }
mindyp16923ee2012-12-10 11:58:29 -0800184 }
185 }
Scott Kennedy771e4172013-06-07 08:48:23 -0700186 if (directoryContactsCursor != null) {
187 try {
188 final Map<String, RecipientEntry> entries =
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800189 processContactEntries(directoryContactsCursor, directoryId);
Scott Kennedy94fa3012013-02-15 18:22:05 -0800190
Scott Kennedy771e4172013-06-07 08:48:23 -0700191 for (final String address : entries.keySet()) {
192 matchesNotFound.remove(address);
193 }
194
195 callback.matchesFound(entries);
196 } finally {
197 directoryContactsCursor.close();
Scott Kennedy94fa3012013-02-15 18:22:05 -0800198 }
mindyp16923ee2012-12-10 11:58:29 -0800199 }
200 }
201 }
202 }
Scott Kennedy94fa3012013-02-15 18:22:05 -0800203
Alon Albert76f1f2d2013-07-14 15:32:49 +0300204 // If no matches found in contact provider or the directories, try the extension
205 // matcher.
206 // todo (aalbert): This whole method needs to be in the adapter?
207 if (adapter != null) {
208 final Map<String, RecipientEntry> entries =
209 adapter.getMatchingRecipients(matchesNotFound);
210 if (entries != null && entries.size() > 0) {
211 callback.matchesFound(entries);
212 for (final String address : entries.keySet()) {
213 matchesNotFound.remove(address);
214 }
215 }
216 }
Scott Kennedy94fa3012013-02-15 18:22:05 -0800217 callback.matchesNotFound(matchesNotFound);
mindyp16923ee2012-12-10 11:58:29 -0800218 }
219
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800220 private static HashMap<String, RecipientEntry> processContactEntries(Cursor c,
221 Long directoryId) {
mindyp16923ee2012-12-10 11:58:29 -0800222 HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>();
223 if (c != null && c.moveToFirst()) {
224 do {
225 String address = c.getString(Queries.Query.DESTINATION);
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800226
227 final RecipientEntry newRecipientEntry = RecipientEntry.constructTopLevelEntry(
mindyp16923ee2012-12-10 11:58:29 -0800228 c.getString(Queries.Query.NAME),
229 c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
230 c.getString(Queries.Query.DESTINATION),
231 c.getInt(Queries.Query.DESTINATION_TYPE),
232 c.getString(Queries.Query.DESTINATION_LABEL),
233 c.getLong(Queries.Query.CONTACT_ID),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800234 directoryId,
mindyp16923ee2012-12-10 11:58:29 -0800235 c.getLong(Queries.Query.DATA_ID),
236 c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
Scott Kennedy514f8a72013-11-13 15:58:08 -0800237 true,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800238 c.getString(Queries.Query.LOOKUP_KEY));
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800239
240 /*
241 * In certain situations, we may have two results for one address, where one of the
242 * results is just the email address, and the other has a name and photo, so we want
243 * to use the better one.
244 */
245 final RecipientEntry recipientEntry =
246 getBetterRecipient(recipientEntries.get(address), newRecipientEntry);
247
248 recipientEntries.put(address, recipientEntry);
mindyp16923ee2012-12-10 11:58:29 -0800249 if (Log.isLoggable(TAG, Log.DEBUG)) {
250 Log.d(TAG, "Received reverse look up information for " + address
251 + " RESULTS: "
252 + " NAME : " + c.getString(Queries.Query.NAME)
253 + " CONTACT ID : " + c.getLong(Queries.Query.CONTACT_ID)
254 + " ADDRESS :" + c.getString(Queries.Query.DESTINATION));
255 }
256 } while (c.moveToNext());
257 }
Mindy Pereira18529312011-06-28 11:00:52 -0700258 return recipientEntries;
259 }
260
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800261 /**
262 * Given two {@link RecipientEntry}s for the same email address, this will return the one that
263 * contains more complete information for display purposes. Defaults to <code>entry2</code> if
264 * no significant differences are found.
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800265 */
Scott Kennedy78f38a02013-02-27 11:21:40 -0800266 static RecipientEntry getBetterRecipient(final RecipientEntry entry1,
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800267 final RecipientEntry entry2) {
268 // If only one has passed in, use it
269 if (entry2 == null) {
270 return entry1;
271 }
272
273 if (entry1 == null) {
274 return entry2;
275 }
276
277 // If only one has a display name, use it
278 if (!TextUtils.isEmpty(entry1.getDisplayName())
279 && TextUtils.isEmpty(entry2.getDisplayName())) {
280 return entry1;
281 }
282
283 if (!TextUtils.isEmpty(entry2.getDisplayName())
284 && TextUtils.isEmpty(entry1.getDisplayName())) {
285 return entry2;
286 }
287
288 // If only one has a display name that is not the same as the destination, use it
289 if (!TextUtils.equals(entry1.getDisplayName(), entry1.getDestination())
290 && TextUtils.equals(entry2.getDisplayName(), entry2.getDestination())) {
291 return entry1;
292 }
293
294 if (!TextUtils.equals(entry2.getDisplayName(), entry2.getDestination())
295 && TextUtils.equals(entry1.getDisplayName(), entry1.getDestination())) {
296 return entry2;
297 }
298
299 // If only one has a photo, use it
300 if ((entry1.getPhotoThumbnailUri() != null || entry1.getPhotoBytes() != null)
301 && (entry2.getPhotoThumbnailUri() == null && entry2.getPhotoBytes() == null)) {
302 return entry1;
303 }
304
305 if ((entry2.getPhotoThumbnailUri() != null || entry2.getPhotoBytes() != null)
306 && (entry1.getPhotoThumbnailUri() == null && entry1.getPhotoBytes() == null)) {
307 return entry2;
308 }
309
310 // Go with the second option as a default
311 return entry2;
312 }
313
mindyp16923ee2012-12-10 11:58:29 -0800314 private static Cursor doQuery(CharSequence constraint, int limit, Long directoryId,
315 Account account, ContentResolver resolver, Query query) {
316 final Uri.Builder builder = query
317 .getContentFilterUri()
318 .buildUpon()
319 .appendPath(constraint.toString())
320 .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
321 String.valueOf(limit + BaseRecipientAdapter.ALLOWANCE_FOR_DUPLICATES));
322 if (directoryId != null) {
323 builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
324 String.valueOf(directoryId));
325 }
326 if (account != null) {
327 builder.appendQueryParameter(BaseRecipientAdapter.PRIMARY_ACCOUNT_NAME, account.name);
328 builder.appendQueryParameter(BaseRecipientAdapter.PRIMARY_ACCOUNT_TYPE, account.type);
329 }
330 final Cursor cursor = resolver.query(builder.build(), query.getProjection(), null, null,
331 null);
332 return cursor;
333 }
334
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800335 public RecipientAlternatesAdapter(Context context, long contactId, Long directoryId,
336 String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener,
337 DropdownChipLayouter dropdownChipLayouter) {
338 super(context,
339 getCursorForConstruction(context, contactId, directoryId, lookupKey, queryMode), 0);
Mindy Pereira97d77682011-06-01 10:48:55 -0700340 mCurrentId = currentId;
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800341 mDirectoryId = directoryId;
Mindy Pereira50863912011-06-16 18:54:10 -0700342 mCheckedItemChangedListener = listener;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700343
Kevin Linb10d1c62014-01-24 12:45:00 -0800344 mDropdownChipLayouter = dropdownChipLayouter;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700345 }
346
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800347 private static Cursor getCursorForConstruction(Context context, long contactId,
348 Long directoryId, String lookupKey, int queryType) {
Makoto Onukia5d37c82012-05-03 13:40:45 -0700349 final Cursor cursor;
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800350 final String desiredMimeType;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700351 if (queryType == QUERY_TYPE_EMAIL) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800352 final Uri uri;
353 final StringBuilder selection = new StringBuilder();
354 selection.append(Queries.EMAIL.getProjection()[Queries.Query.CONTACT_ID]);
355 selection.append(" = ?");
356
357 if (directoryId == null || lookupKey == null) {
358 uri = Queries.EMAIL.getContentUri();
359 desiredMimeType = null;
360 } else {
361 final Uri.Builder builder = Contacts.getLookupUri(contactId, lookupKey).buildUpon();
362 builder.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
363 .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
364 String.valueOf(directoryId));
365 uri = builder.build();
366 desiredMimeType = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
367 }
Makoto Onukia5d37c82012-05-03 13:40:45 -0700368 cursor = context.getContentResolver().query(
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800369 uri,
Tom Taylor80f4abf2012-04-06 13:37:20 -0700370 Queries.EMAIL.getProjection(),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800371 selection.toString(), new String[] {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700372 String.valueOf(contactId)
373 }, null);
374 } else {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800375 final Uri uri;
376 final StringBuilder selection = new StringBuilder();
377 selection.append(Queries.PHONE.getProjection()[Queries.Query.CONTACT_ID]);
378 selection.append(" = ?");
379
380 if (lookupKey == null) {
381 uri = Queries.PHONE.getContentUri();
382 desiredMimeType = null;
383 } else {
384 final Uri.Builder builder = Contacts.getLookupUri(contactId, lookupKey).buildUpon();
385 builder.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
386 .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
387 String.valueOf(directoryId));
388 uri = builder.build();
389 desiredMimeType = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
390 }
Makoto Onukia5d37c82012-05-03 13:40:45 -0700391 cursor = context.getContentResolver().query(
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800392 uri,
Tom Taylor80f4abf2012-04-06 13:37:20 -0700393 Queries.PHONE.getProjection(),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800394 selection.toString(), new String[] {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700395 String.valueOf(contactId)
396 }, null);
397 }
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800398
399 final Cursor resultCursor = removeUndesiredDestinations(cursor, desiredMimeType, lookupKey);
400 cursor.close();
401
402 return resultCursor;
Makoto Onukia5d37c82012-05-03 13:40:45 -0700403 }
404
405 /**
406 * @return a new cursor based on the given cursor with all duplicate destinations removed.
407 *
408 * It's only intended to use for the alternate list, so...
409 * - This method ignores all other fields and dedupe solely on the destination. Normally,
410 * if a cursor contains multiple contacts and they have the same destination, we'd still want
411 * to show both.
412 * - This method creates a MatrixCursor, so all data will be kept in memory. We wouldn't want
413 * to do this if the original cursor is large, but it's okay here because the alternate list
414 * won't be that big.
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800415 *
416 * @param desiredMimeType If this is non-<code>null</code>, only entries with this mime type
417 * will be added to the cursor
418 * @param lookupKey The lookup key used for this contact if there isn't one in the cursor. This
419 * should be the same one used in the query that returned the cursor
Makoto Onukia5d37c82012-05-03 13:40:45 -0700420 */
421 // Visible for testing
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800422 static Cursor removeUndesiredDestinations(final Cursor original, final String desiredMimeType,
423 final String lookupKey) {
Makoto Onukia5d37c82012-05-03 13:40:45 -0700424 final MatrixCursor result = new MatrixCursor(
425 original.getColumnNames(), original.getCount());
426 final HashSet<String> destinationsSeen = new HashSet<String>();
427
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800428 String defaultDisplayName = null;
429 String defaultPhotoThumbnailUri = null;
430 int defaultDisplayNameSource = 0;
431
432 // Find some nice defaults in case we need them
Makoto Onukia5d37c82012-05-03 13:40:45 -0700433 original.moveToPosition(-1);
434 while (original.moveToNext()) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800435 final String mimeType = original.getString(Query.MIME_TYPE);
436
437 if (ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(
438 mimeType)) {
439 // Store this data
440 defaultDisplayName = original.getString(Query.NAME);
441 defaultPhotoThumbnailUri = original.getString(Query.PHOTO_THUMBNAIL_URI);
442 defaultDisplayNameSource = original.getInt(Query.DISPLAY_NAME_SOURCE);
443 break;
444 }
445 }
446
447 original.moveToPosition(-1);
448 while (original.moveToNext()) {
449 if (desiredMimeType != null) {
450 final String mimeType = original.getString(Query.MIME_TYPE);
451 if (!desiredMimeType.equals(mimeType)) {
452 continue;
453 }
454 }
Makoto Onukia5d37c82012-05-03 13:40:45 -0700455 final String destination = original.getString(Query.DESTINATION);
456 if (destinationsSeen.contains(destination)) {
457 continue;
458 }
459 destinationsSeen.add(destination);
460
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800461 final Object[] row = new Object[] {
Makoto Onukia5d37c82012-05-03 13:40:45 -0700462 original.getString(Query.NAME),
463 original.getString(Query.DESTINATION),
464 original.getInt(Query.DESTINATION_TYPE),
465 original.getString(Query.DESTINATION_LABEL),
466 original.getLong(Query.CONTACT_ID),
467 original.getLong(Query.DATA_ID),
468 original.getString(Query.PHOTO_THUMBNAIL_URI),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800469 original.getInt(Query.DISPLAY_NAME_SOURCE),
470 original.getString(Query.LOOKUP_KEY),
471 original.getString(Query.MIME_TYPE)
472 };
473
474 if (row[Query.NAME] == null) {
475 row[Query.NAME] = defaultDisplayName;
476 }
477 if (row[Query.PHOTO_THUMBNAIL_URI] == null) {
478 row[Query.PHOTO_THUMBNAIL_URI] = defaultPhotoThumbnailUri;
479 }
480 if ((Integer) row[Query.DISPLAY_NAME_SOURCE] == 0) {
481 row[Query.DISPLAY_NAME_SOURCE] = defaultDisplayNameSource;
482 }
483 if (row[Query.LOOKUP_KEY] == null) {
484 row[Query.LOOKUP_KEY] = lookupKey;
485 }
486
487 // Ensure we don't have two '?' like content://.../...?account_name=...?sz=...
488 final String photoThumbnailUri = (String) row[Query.PHOTO_THUMBNAIL_URI];
489 if (photoThumbnailUri != null) {
490 if (sCorrectedPhotoUris.containsKey(photoThumbnailUri)) {
491 row[Query.PHOTO_THUMBNAIL_URI] = sCorrectedPhotoUris.get(photoThumbnailUri);
492 } else if (photoThumbnailUri.indexOf('?') != photoThumbnailUri.lastIndexOf('?')) {
493 final String[] parts = photoThumbnailUri.split("\\?");
494 final StringBuilder correctedUriBuilder = new StringBuilder();
495 for (int i = 0; i < parts.length; i++) {
496 if (i == 1) {
497 correctedUriBuilder.append("?"); // We only want one of these
498 } else if (i > 1) {
499 correctedUriBuilder.append("&"); // And we want these elsewhere
500 }
501 correctedUriBuilder.append(parts[i]);
502 }
503
504 final String correctedUri = correctedUriBuilder.toString();
505 sCorrectedPhotoUris.put(photoThumbnailUri, correctedUri);
506 row[Query.PHOTO_THUMBNAIL_URI] = correctedUri;
507 }
508 }
509
510 result.addRow(row);
Makoto Onukia5d37c82012-05-03 13:40:45 -0700511 }
512
513 return result;
Mindy Pereira97d77682011-06-01 10:48:55 -0700514 }
515
516 @Override
517 public long getItemId(int position) {
518 Cursor c = getCursor();
Mindy Pereira3bb52162011-07-01 14:52:22 -0700519 if (c.moveToPosition(position)) {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700520 c.getLong(Queries.Query.DATA_ID);
Mindy Pereira3bb52162011-07-01 14:52:22 -0700521 }
522 return -1;
Mindy Pereira97d77682011-06-01 10:48:55 -0700523 }
524
525 public RecipientEntry getRecipientEntry(int position) {
526 Cursor c = getCursor();
527 c.moveToPosition(position);
Tom Taylor80f4abf2012-04-06 13:37:20 -0700528 return RecipientEntry.constructTopLevelEntry(
529 c.getString(Queries.Query.NAME),
Makoto Onuki00adb322012-05-01 16:50:41 -0700530 c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
Tom Taylor80f4abf2012-04-06 13:37:20 -0700531 c.getString(Queries.Query.DESTINATION),
532 c.getInt(Queries.Query.DESTINATION_TYPE),
533 c.getString(Queries.Query.DESTINATION_LABEL),
534 c.getLong(Queries.Query.CONTACT_ID),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800535 mDirectoryId,
Tom Taylor80f4abf2012-04-06 13:37:20 -0700536 c.getLong(Queries.Query.DATA_ID),
mindypccb8e232012-11-14 11:59:27 -0800537 c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
Scott Kennedy514f8a72013-11-13 15:58:08 -0800538 true,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800539 c.getString(Queries.Query.LOOKUP_KEY));
Mindy Pereira97d77682011-06-01 10:48:55 -0700540 }
541
542 @Override
543 public View getView(int position, View convertView, ViewGroup parent) {
Mindy Pereira67ad4892011-06-02 12:21:22 -0700544 Cursor cursor = getCursor();
545 cursor.moveToPosition(position);
Mindy Pereira97d77682011-06-01 10:48:55 -0700546 if (convertView == null) {
Kevin Linb10d1c62014-01-24 12:45:00 -0800547 convertView = mDropdownChipLayouter.newView();
Mindy Pereira007a76b2011-06-14 11:39:36 -0700548 }
Tom Taylor80f4abf2012-04-06 13:37:20 -0700549 if (cursor.getLong(Queries.Query.DATA_ID) == mCurrentId) {
Mindy Pereira007a76b2011-06-14 11:39:36 -0700550 mCheckedItemPosition = position;
Mindy Pereira50863912011-06-16 18:54:10 -0700551 if (mCheckedItemChangedListener != null) {
552 mCheckedItemChangedListener.onCheckedItemChanged(mCheckedItemPosition);
553 }
Mindy Pereira97d77682011-06-01 10:48:55 -0700554 }
Mindy Pereira67ad4892011-06-02 12:21:22 -0700555 bindView(convertView, convertView.getContext(), cursor);
Mindy Pereira97d77682011-06-01 10:48:55 -0700556 return convertView;
557 }
558
559 @Override
560 public void bindView(View view, Context context, Cursor cursor) {
Mindy Pereira67ad4892011-06-02 12:21:22 -0700561 int position = cursor.getPosition();
Mindy Pereira67ad4892011-06-02 12:21:22 -0700562 RecipientEntry entry = getRecipientEntry(position);
Daisuke Miyakawa102cf102011-07-21 08:48:27 -0700563
Kevin Linb10d1c62014-01-24 12:45:00 -0800564 mDropdownChipLayouter.bindView(view, null, entry, position,
565 AdapterType.RECIPIENT_ALTERNATES, null);
Mindy Pereira97d77682011-06-01 10:48:55 -0700566 }
567
568 @Override
569 public View newView(Context context, Cursor cursor, ViewGroup parent) {
Kevin Linb10d1c62014-01-24 12:45:00 -0800570 return mDropdownChipLayouter.newView();
Mindy Pereira007a76b2011-06-14 11:39:36 -0700571 }
572
Mindy Pereira50863912011-06-16 18:54:10 -0700573 /*package*/ static interface OnCheckedItemChangedListener {
574 public void onCheckedItemChanged(int position);
Mindy Pereira97d77682011-06-01 10:48:55 -0700575 }
576}