blob: 35ff2a7933eede0c29e1b5f037c312fdf22b3aeb [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;
Jin Cao4db8ccc2014-07-30 10:11:07 -070024import android.graphics.drawable.StateListDrawable;
mindyp16923ee2012-12-10 11:58:29 -080025import android.net.Uri;
26import android.provider.ContactsContract;
Scott Kennedy7a4e6772013-11-21 14:31:33 -080027import android.provider.ContactsContract.Contacts;
Scott Kennedyef3f76c2013-02-25 10:48:25 -080028import android.text.TextUtils;
Mindy Pereira18529312011-06-28 11:00:52 -070029import android.text.util.Rfc822Token;
30import android.text.util.Rfc822Tokenizer;
Mindy Pereira18987c42011-07-11 11:26:46 -070031import android.util.Log;
Mindy Pereira97d77682011-06-01 10:48:55 -070032import android.view.View;
33import android.view.ViewGroup;
34import android.widget.CursorAdapter;
Mindy Pereira97d77682011-06-01 10:48:55 -070035
mindyp16923ee2012-12-10 11:58:29 -080036import com.android.ex.chips.BaseRecipientAdapter.DirectoryListQuery;
37import com.android.ex.chips.BaseRecipientAdapter.DirectorySearchParams;
Kevin Linb10d1c62014-01-24 12:45:00 -080038import com.android.ex.chips.DropdownChipLayouter.AdapterType;
Tom Taylor80f4abf2012-04-06 13:37:20 -070039import com.android.ex.chips.Queries.Query;
Makoto Onukia5d37c82012-05-03 13:40:45 -070040
Mindy Pereira03cfe3e2012-05-24 13:21:59 -070041import java.util.ArrayList;
Mindy Pereira18529312011-06-28 11:00:52 -070042import java.util.HashMap;
Makoto Onukia5d37c82012-05-03 13:40:45 -070043import java.util.HashSet;
mindyp16923ee2012-12-10 11:58:29 -080044import java.util.List;
Scott Kennedy94fa3012013-02-15 18:22:05 -080045import java.util.Map;
46import java.util.Set;
Mindy Pereira18529312011-06-28 11:00:52 -070047
Tom Taylor80f4abf2012-04-06 13:37:20 -070048/**
49 * RecipientAlternatesAdapter backs the RecipientEditTextView for managing contacts
50 * queried by email or by phone number.
51 */
Mindy Pereira97d77682011-06-01 10:48:55 -070052public class RecipientAlternatesAdapter extends CursorAdapter {
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -070053 public static final int MAX_LOOKUPS = 50;
Mindy Pereira97d77682011-06-01 10:48:55 -070054
Mindy Pereira97d77682011-06-01 10:48:55 -070055 private final long mCurrentId;
56
Mindy Pereira007a76b2011-06-14 11:39:36 -070057 private int mCheckedItemPosition = -1;
58
Mindy Pereira50863912011-06-16 18:54:10 -070059 private OnCheckedItemChangedListener mCheckedItemChangedListener;
60
Mindy Pereira18987c42011-07-11 11:26:46 -070061 private static final String TAG = "RecipAlternates";
62
Tom Taylor80f4abf2012-04-06 13:37:20 -070063 public static final int QUERY_TYPE_EMAIL = 0;
64 public static final int QUERY_TYPE_PHONE = 1;
Scott Kennedy7a4e6772013-11-21 14:31:33 -080065 private final Long mDirectoryId;
Kevin Linb10d1c62014-01-24 12:45:00 -080066 private DropdownChipLayouter mDropdownChipLayouter;
Jin Cao4db8ccc2014-07-30 10:11:07 -070067 private final StateListDrawable mDeleteDrawable;
Tom Taylor80f4abf2012-04-06 13:37:20 -070068
Scott Kennedy7a4e6772013-11-21 14:31:33 -080069 private static final Map<String, String> sCorrectedPhotoUris = new HashMap<String, String>();
70
mindyp16923ee2012-12-10 11:58:29 -080071 public interface RecipientMatchCallback {
Scott Kennedy94fa3012013-02-15 18:22:05 -080072 public void matchesFound(Map<String, RecipientEntry> results);
73 /**
74 * Called with all addresses that could not be resolved to valid recipients.
75 */
Scott Kennedyf7e202d2013-03-06 21:38:10 -080076 public void matchesNotFound(Set<String> unfoundAddresses);
mindyp16923ee2012-12-10 11:58:29 -080077 }
78
Alon Albert76f1f2d2013-07-14 15:32:49 +030079 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);
Tom Taylor80f4abf2012-04-06 13:37:20 -070082 }
83
Mindy Pereira18529312011-06-28 11:00:52 -070084 /**
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.
Daisuke Miyakawa72117472011-07-20 14:10:10 -070090 * @param inAddresses Array of addresses on which to perform the lookup.
mindyp16923ee2012-12-10 11:58:29 -080091 * @param callback RecipientMatchCallback called when a match or matches are found.
Mindy Pereira18529312011-06-28 11:00:52 -070092 */
Alon Albert76f1f2d2013-07-14 15:32:49 +030093 public static void getMatchingRecipients(Context context, BaseRecipientAdapter adapter,
94 ArrayList<String> inAddresses, int addressType, Account account,
95 RecipientMatchCallback callback) {
Tom Taylor80f4abf2012-04-06 13:37:20 -070096 Queries.Query query;
97 if (addressType == QUERY_TYPE_EMAIL) {
98 query = Queries.EMAIL;
99 } else {
100 query = Queries.PHONE;
101 }
Mindy Pereira03cfe3e2012-05-24 13:21:59 -0700102 int addressesSize = Math.min(MAX_LOOKUPS, inAddresses.size());
mindyp16923ee2012-12-10 11:58:29 -0800103 HashSet<String> addresses = new HashSet<String>();
Mindy Pereira18529312011-06-28 11:00:52 -0700104 StringBuilder bindString = new StringBuilder();
105 // Create the "?" string and set up arguments.
106 for (int i = 0; i < addressesSize; i++) {
Mindy Pereira03cfe3e2012-05-24 13:21:59 -0700107 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses.get(i).toLowerCase());
mindyp16923ee2012-12-10 11:58:29 -0800108 addresses.add(tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i));
Mindy Pereira18529312011-06-28 11:00:52 -0700109 bindString.append("?");
110 if (i < addressesSize - 1) {
111 bindString.append(",");
112 }
113 }
114
Mindy Pereira18987c42011-07-11 11:26:46 -0700115 if (Log.isLoggable(TAG, Log.DEBUG)) {
116 Log.d(TAG, "Doing reverse lookup for " + addresses.toString());
117 }
118
mindyp16923ee2012-12-10 11:58:29 -0800119 String[] addressArray = new String[addresses.size()];
120 addresses.toArray(addressArray);
121 HashMap<String, RecipientEntry> recipientEntries = null;
122 Cursor c = null;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700123
mindyp16923ee2012-12-10 11:58:29 -0800124 try {
125 c = context.getContentResolver().query(
126 query.getContentUri(),
127 query.getProjection(),
128 query.getProjection()[Queries.Query.DESTINATION] + " IN ("
129 + bindString.toString() + ")", addressArray, null);
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800130 recipientEntries = processContactEntries(c, null /* directoryId */);
mindyp16923ee2012-12-10 11:58:29 -0800131 callback.matchesFound(recipientEntries);
132 } finally {
133 if (c != null) {
Mindy Pereira18529312011-06-28 11:00:52 -0700134 c.close();
135 }
136 }
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700137
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) {
mindyp16923ee2012-12-10 11:58:29 -0800159 // See if any entries did not resolve; if so, we need to check other
160 // directories
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700161
mindyp16923ee2012-12-10 11:58:29 -0800162 if (recipientEntries.size() < addresses.size()) {
163 final List<DirectorySearchParams> paramsList;
Scott Kennedy539c2b12013-03-13 19:25:12 -0700164 Cursor directoryCursor = null;
165 try {
166 directoryCursor = context.getContentResolver().query(DirectoryListQuery.URI,
167 DirectoryListQuery.PROJECTION, null, null, null);
Scott Kennedy771e4172013-06-07 08:48:23 -0700168 if (directoryCursor == null) {
169 paramsList = null;
170 } else {
171 paramsList = BaseRecipientAdapter.setupOtherDirectories(context,
172 directoryCursor, account);
173 }
Scott Kennedy539c2b12013-03-13 19:25:12 -0700174 } finally {
175 if (directoryCursor != null) {
176 directoryCursor.close();
177 }
178 }
mindyp16923ee2012-12-10 11:58:29 -0800179 // 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 }
Scott Kennedy94fa3012013-02-15 18:22:05 -0800186
187 matchesNotFound.addAll(unresolvedAddresses);
188
Scott Kennedy771e4172013-06-07 08:48:23 -0700189 if (paramsList != null) {
190 Cursor directoryContactsCursor = null;
191 for (String unresolvedAddress : unresolvedAddresses) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800192 Long directoryId = null;
Scott Kennedy771e4172013-06-07 08:48:23 -0700193 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 {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800204 directoryId = paramsList.get(i).directoryId;
Scott Kennedy771e4172013-06-07 08:48:23 -0700205 break;
206 }
mindyp16923ee2012-12-10 11:58:29 -0800207 }
208 }
Scott Kennedy771e4172013-06-07 08:48:23 -0700209 if (directoryContactsCursor != null) {
210 try {
211 final Map<String, RecipientEntry> entries =
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800212 processContactEntries(directoryContactsCursor, directoryId);
Scott Kennedy94fa3012013-02-15 18:22:05 -0800213
Scott Kennedy771e4172013-06-07 08:48:23 -0700214 for (final String address : entries.keySet()) {
215 matchesNotFound.remove(address);
216 }
217
218 callback.matchesFound(entries);
219 } finally {
220 directoryContactsCursor.close();
Scott Kennedy94fa3012013-02-15 18:22:05 -0800221 }
mindyp16923ee2012-12-10 11:58:29 -0800222 }
223 }
224 }
225 }
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700226 }
Scott Kennedy94fa3012013-02-15 18:22:05 -0800227
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700228 public static void getMatchingRecipientsFromExtensionMatcher(BaseRecipientAdapter adapter,
229 Set<String> matchesNotFound, RecipientMatchCallback callback) {
Alon Albert76f1f2d2013-07-14 15:32:49 +0300230 // 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 }
Scott Kennedy94fa3012013-02-15 18:22:05 -0800243 callback.matchesNotFound(matchesNotFound);
mindyp16923ee2012-12-10 11:58:29 -0800244 }
245
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800246 private static HashMap<String, RecipientEntry> processContactEntries(Cursor c,
247 Long directoryId) {
mindyp16923ee2012-12-10 11:58:29 -0800248 HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>();
249 if (c != null && c.moveToFirst()) {
250 do {
251 String address = c.getString(Queries.Query.DESTINATION);
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800252
253 final RecipientEntry newRecipientEntry = RecipientEntry.constructTopLevelEntry(
mindyp16923ee2012-12-10 11:58:29 -0800254 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),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800260 directoryId,
mindyp16923ee2012-12-10 11:58:29 -0800261 c.getLong(Queries.Query.DATA_ID),
262 c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
Scott Kennedy514f8a72013-11-13 15:58:08 -0800263 true,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800264 c.getString(Queries.Query.LOOKUP_KEY));
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800265
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);
mindyp16923ee2012-12-10 11:58:29 -0800275 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 }
Mindy Pereira18529312011-06-28 11:00:52 -0700284 return recipientEntries;
285 }
286
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800287 /**
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.
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800291 */
Scott Kennedy78f38a02013-02-27 11:21:40 -0800292 static RecipientEntry getBetterRecipient(final RecipientEntry entry1,
Scott Kennedyef3f76c2013-02-25 10:48:25 -0800293 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
mindyp16923ee2012-12-10 11:58:29 -0800340 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
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800361 public RecipientAlternatesAdapter(Context context, long contactId, Long directoryId,
362 String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener,
363 DropdownChipLayouter dropdownChipLayouter) {
Jin Cao4db8ccc2014-07-30 10:11:07 -0700364 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) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800371 super(context,
372 getCursorForConstruction(context, contactId, directoryId, lookupKey, queryMode), 0);
Mindy Pereira97d77682011-06-01 10:48:55 -0700373 mCurrentId = currentId;
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800374 mDirectoryId = directoryId;
Mindy Pereira50863912011-06-16 18:54:10 -0700375 mCheckedItemChangedListener = listener;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700376
Kevin Linb10d1c62014-01-24 12:45:00 -0800377 mDropdownChipLayouter = dropdownChipLayouter;
Jin Cao4db8ccc2014-07-30 10:11:07 -0700378 mDeleteDrawable = deleteDrawable;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700379 }
380
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800381 private static Cursor getCursorForConstruction(Context context, long contactId,
382 Long directoryId, String lookupKey, int queryType) {
Makoto Onukia5d37c82012-05-03 13:40:45 -0700383 final Cursor cursor;
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800384 final String desiredMimeType;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700385 if (queryType == QUERY_TYPE_EMAIL) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800386 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 }
Makoto Onukia5d37c82012-05-03 13:40:45 -0700402 cursor = context.getContentResolver().query(
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800403 uri,
Tom Taylor80f4abf2012-04-06 13:37:20 -0700404 Queries.EMAIL.getProjection(),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800405 selection.toString(), new String[] {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700406 String.valueOf(contactId)
407 }, null);
408 } else {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800409 final Uri uri;
410 final StringBuilder selection = new StringBuilder();
411 selection.append(Queries.PHONE.getProjection()[Queries.Query.CONTACT_ID]);
412 selection.append(" = ?");
413
Filipe3621aa12015-10-21 17:14:11 +0000414 if (directoryId == null || lookupKey == null) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800415 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 }
Makoto Onukia5d37c82012-05-03 13:40:45 -0700425 cursor = context.getContentResolver().query(
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800426 uri,
Tom Taylor80f4abf2012-04-06 13:37:20 -0700427 Queries.PHONE.getProjection(),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800428 selection.toString(), new String[] {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700429 String.valueOf(contactId)
430 }, null);
431 }
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800432
433 final Cursor resultCursor = removeUndesiredDestinations(cursor, desiredMimeType, lookupKey);
434 cursor.close();
435
436 return resultCursor;
Makoto Onukia5d37c82012-05-03 13:40:45 -0700437 }
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.
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800449 *
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
Makoto Onukia5d37c82012-05-03 13:40:45 -0700454 */
455 // Visible for testing
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800456 static Cursor removeUndesiredDestinations(final Cursor original, final String desiredMimeType,
457 final String lookupKey) {
Makoto Onukia5d37c82012-05-03 13:40:45 -0700458 final MatrixCursor result = new MatrixCursor(
459 original.getColumnNames(), original.getCount());
460 final HashSet<String> destinationsSeen = new HashSet<String>();
461
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800462 String defaultDisplayName = null;
463 String defaultPhotoThumbnailUri = null;
464 int defaultDisplayNameSource = 0;
465
466 // Find some nice defaults in case we need them
Makoto Onukia5d37c82012-05-03 13:40:45 -0700467 original.moveToPosition(-1);
468 while (original.moveToNext()) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800469 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 }
Makoto Onukia5d37c82012-05-03 13:40:45 -0700489 final String destination = original.getString(Query.DESTINATION);
490 if (destinationsSeen.contains(destination)) {
491 continue;
492 }
493 destinationsSeen.add(destination);
494
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800495 final Object[] row = new Object[] {
Makoto Onukia5d37c82012-05-03 13:40:45 -0700496 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),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800503 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);
Makoto Onukia5d37c82012-05-03 13:40:45 -0700545 }
546
547 return result;
Mindy Pereira97d77682011-06-01 10:48:55 -0700548 }
549
550 @Override
551 public long getItemId(int position) {
552 Cursor c = getCursor();
Mindy Pereira3bb52162011-07-01 14:52:22 -0700553 if (c.moveToPosition(position)) {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700554 c.getLong(Queries.Query.DATA_ID);
Mindy Pereira3bb52162011-07-01 14:52:22 -0700555 }
556 return -1;
Mindy Pereira97d77682011-06-01 10:48:55 -0700557 }
558
559 public RecipientEntry getRecipientEntry(int position) {
560 Cursor c = getCursor();
561 c.moveToPosition(position);
Tom Taylor80f4abf2012-04-06 13:37:20 -0700562 return RecipientEntry.constructTopLevelEntry(
563 c.getString(Queries.Query.NAME),
Makoto Onuki00adb322012-05-01 16:50:41 -0700564 c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
Tom Taylor80f4abf2012-04-06 13:37:20 -0700565 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),
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800569 mDirectoryId,
Tom Taylor80f4abf2012-04-06 13:37:20 -0700570 c.getLong(Queries.Query.DATA_ID),
mindypccb8e232012-11-14 11:59:27 -0800571 c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
Scott Kennedy514f8a72013-11-13 15:58:08 -0800572 true,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800573 c.getString(Queries.Query.LOOKUP_KEY));
Mindy Pereira97d77682011-06-01 10:48:55 -0700574 }
575
576 @Override
577 public View getView(int position, View convertView, ViewGroup parent) {
Mindy Pereira67ad4892011-06-02 12:21:22 -0700578 Cursor cursor = getCursor();
579 cursor.moveToPosition(position);
Mindy Pereira97d77682011-06-01 10:48:55 -0700580 if (convertView == null) {
Jin Cao4ddcdae2014-07-28 19:03:56 -0700581 convertView = mDropdownChipLayouter.newView(AdapterType.RECIPIENT_ALTERNATES);
Mindy Pereira007a76b2011-06-14 11:39:36 -0700582 }
Tom Taylor80f4abf2012-04-06 13:37:20 -0700583 if (cursor.getLong(Queries.Query.DATA_ID) == mCurrentId) {
Mindy Pereira007a76b2011-06-14 11:39:36 -0700584 mCheckedItemPosition = position;
Mindy Pereira50863912011-06-16 18:54:10 -0700585 if (mCheckedItemChangedListener != null) {
586 mCheckedItemChangedListener.onCheckedItemChanged(mCheckedItemPosition);
587 }
Mindy Pereira97d77682011-06-01 10:48:55 -0700588 }
Mindy Pereira67ad4892011-06-02 12:21:22 -0700589 bindView(convertView, convertView.getContext(), cursor);
Mindy Pereira97d77682011-06-01 10:48:55 -0700590 return convertView;
591 }
592
593 @Override
594 public void bindView(View view, Context context, Cursor cursor) {
Mindy Pereira67ad4892011-06-02 12:21:22 -0700595 int position = cursor.getPosition();
Mindy Pereira67ad4892011-06-02 12:21:22 -0700596 RecipientEntry entry = getRecipientEntry(position);
Daisuke Miyakawa102cf102011-07-21 08:48:27 -0700597
Kevin Linb10d1c62014-01-24 12:45:00 -0800598 mDropdownChipLayouter.bindView(view, null, entry, position,
Jin Cao4db8ccc2014-07-30 10:11:07 -0700599 AdapterType.RECIPIENT_ALTERNATES, null, mDeleteDrawable);
Mindy Pereira97d77682011-06-01 10:48:55 -0700600 }
601
602 @Override
603 public View newView(Context context, Cursor cursor, ViewGroup parent) {
Jin Cao4ddcdae2014-07-28 19:03:56 -0700604 return mDropdownChipLayouter.newView(AdapterType.RECIPIENT_ALTERNATES);
Mindy Pereira007a76b2011-06-14 11:39:36 -0700605 }
606
Mindy Pereira50863912011-06-16 18:54:10 -0700607 /*package*/ static interface OnCheckedItemChangedListener {
608 public void onCheckedItemChanged(int position);
Mindy Pereira97d77682011-06-01 10:48:55 -0700609 }
610}