blob: 151bdcd5ba46cdde92a5abc3042b7161ac53421e [file] [log] [blame]
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -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 */
16
17package com.android.ex.chips;
18
19import android.accounts.Account;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.pm.PackageManager;
23import android.content.pm.PackageManager.NameNotFoundException;
24import android.content.res.Resources;
25import android.database.Cursor;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -070026import android.net.Uri;
27import android.os.Handler;
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -070028import android.os.Message;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -070029import android.provider.ContactsContract;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -070030import android.provider.ContactsContract.Directory;
31import android.text.TextUtils;
32import android.text.util.Rfc822Token;
33import android.util.Log;
34import android.view.View;
35import android.view.ViewGroup;
36import android.widget.AutoCompleteTextView;
37import android.widget.BaseAdapter;
38import android.widget.Filter;
39import android.widget.Filterable;
Kevin Linb10d1c62014-01-24 12:45:00 -080040
41import com.android.ex.chips.DropdownChipLayouter.AdapterType;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -070042
43import java.util.ArrayList;
Scott Kennedyfa7b0fb2014-04-18 14:40:22 -070044import java.util.Collections;
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -070045import java.util.HashSet;
Daisuke Miyakawacf9337a2011-05-31 09:56:01 -070046import java.util.LinkedHashMap;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -070047import java.util.List;
48import java.util.Map;
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -070049import java.util.Set;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -070050
51/**
52 * Adapter for showing a recipient list.
53 */
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -070054public class BaseRecipientAdapter extends BaseAdapter implements Filterable, AccountSpecifier,
55 PhotoManager.PhotoManagerCallback {
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -070056 private static final String TAG = "BaseRecipientAdapter";
Daisuke Miyakawa8383f442011-07-05 13:58:29 -070057
Daisuke Miyakawa6b616f12011-09-19 17:08:40 -070058 private static final boolean DEBUG = false;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -070059
60 /**
61 * The preferred number of results to be retrieved. This number may be
62 * exceeded if there are several directories configured, because we will use
63 * the same limit for all directories.
64 */
65 private static final int DEFAULT_PREFERRED_MAX_RESULT_COUNT = 10;
66
67 /**
68 * The number of extra entries requested to allow for duplicates. Duplicates
69 * are removed from the overall result.
70 */
mindyp16923ee2012-12-10 11:58:29 -080071 static final int ALLOWANCE_FOR_DUPLICATES = 5;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -070072
Daisuke Miyakawa7537f842011-05-31 15:47:25 -070073 // This is ContactsContract.PRIMARY_ACCOUNT_NAME. Available from ICS as hidden
mindyp16923ee2012-12-10 11:58:29 -080074 static final String PRIMARY_ACCOUNT_NAME = "name_for_primary_account";
Daisuke Miyakawa7537f842011-05-31 15:47:25 -070075 // This is ContactsContract.PRIMARY_ACCOUNT_TYPE. Available from ICS as hidden
mindyp16923ee2012-12-10 11:58:29 -080076 static final String PRIMARY_ACCOUNT_TYPE = "type_for_primary_account";
Daisuke Miyakawa7537f842011-05-31 15:47:25 -070077
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -070078 /**
79 * The "Waiting for more contacts" message will be displayed if search is not complete
80 * within this many milliseconds.
81 */
82 private static final int MESSAGE_SEARCH_PENDING_DELAY = 1000;
83 /** Used to prepare "Waiting for more contacts" message. */
84 private static final int MESSAGE_SEARCH_PENDING = 1;
85
Daisuke Miyakawad4baa3f2011-05-26 09:24:00 -070086 public static final int QUERY_TYPE_EMAIL = 0;
87 public static final int QUERY_TYPE_PHONE = 1;
88
Andrew Sapperstein1db635b2014-04-29 13:07:57 -070089 private final Queries.Query mQueryMode;
Tom Taylor80f4abf2012-04-06 13:37:20 -070090 private final int mQueryType;
91
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -070092 /**
93 * Model object for a {@link Directory} row.
94 */
95 public final static class DirectorySearchParams {
96 public long directoryId;
97 public String directoryType;
98 public String displayName;
99 public String accountName;
100 public String accountType;
101 public CharSequence constraint;
102 public DirectoryFilter filter;
103 }
104
mindyp16923ee2012-12-10 11:58:29 -0800105 protected static class DirectoryListQuery {
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700106
107 public static final Uri URI =
108 Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "directories");
109 public static final String[] PROJECTION = {
110 Directory._ID, // 0
111 Directory.ACCOUNT_NAME, // 1
112 Directory.ACCOUNT_TYPE, // 2
113 Directory.DISPLAY_NAME, // 3
114 Directory.PACKAGE_NAME, // 4
115 Directory.TYPE_RESOURCE_ID, // 5
116 };
117
118 public static final int ID = 0;
119 public static final int ACCOUNT_NAME = 1;
120 public static final int ACCOUNT_TYPE = 2;
121 public static final int DISPLAY_NAME = 3;
122 public static final int PACKAGE_NAME = 4;
123 public static final int TYPE_RESOURCE_ID = 5;
124 }
125
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700126 /** Used to temporarily hold results in Cursor objects. */
Alon Albert76f1f2d2013-07-14 15:32:49 +0300127 protected static class TemporaryEntry {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700128 public final String displayName;
129 public final String destination;
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700130 public final int destinationType;
131 public final String destinationLabel;
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700132 public final long contactId;
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800133 public final Long directoryId;
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700134 public final long dataId;
135 public final String thumbnailUriString;
Makoto Onuki00adb322012-05-01 16:50:41 -0700136 public final int displayNameSource;
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800137 public final String lookupKey;
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700138
Alon Albert76f1f2d2013-07-14 15:32:49 +0300139 public TemporaryEntry(
140 String displayName,
141 String destination,
142 int destinationType,
143 String destinationLabel,
144 long contactId,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800145 Long directoryId,
Alon Albert76f1f2d2013-07-14 15:32:49 +0300146 long dataId,
147 String thumbnailUriString,
Scott Kennedy514f8a72013-11-13 15:58:08 -0800148 int displayNameSource,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800149 String lookupKey) {
Alon Albert76f1f2d2013-07-14 15:32:49 +0300150 this.displayName = displayName;
151 this.destination = destination;
152 this.destinationType = destinationType;
153 this.destinationLabel = destinationLabel;
154 this.contactId = contactId;
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800155 this.directoryId = directoryId;
Alon Albert76f1f2d2013-07-14 15:32:49 +0300156 this.dataId = dataId;
157 this.thumbnailUriString = thumbnailUriString;
158 this.displayNameSource = displayNameSource;
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800159 this.lookupKey = lookupKey;
Alon Albert76f1f2d2013-07-14 15:32:49 +0300160 }
161
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800162 public TemporaryEntry(Cursor cursor, Long directoryId) {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700163 this.displayName = cursor.getString(Queries.Query.NAME);
164 this.destination = cursor.getString(Queries.Query.DESTINATION);
165 this.destinationType = cursor.getInt(Queries.Query.DESTINATION_TYPE);
166 this.destinationLabel = cursor.getString(Queries.Query.DESTINATION_LABEL);
167 this.contactId = cursor.getLong(Queries.Query.CONTACT_ID);
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800168 this.directoryId = directoryId;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700169 this.dataId = cursor.getLong(Queries.Query.DATA_ID);
170 this.thumbnailUriString = cursor.getString(Queries.Query.PHOTO_THUMBNAIL_URI);
Makoto Onuki00adb322012-05-01 16:50:41 -0700171 this.displayNameSource = cursor.getInt(Queries.Query.DISPLAY_NAME_SOURCE);
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800172 this.lookupKey = cursor.getString(Queries.Query.LOOKUP_KEY);
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700173 }
174 }
175
176 /**
177 * Used to pass results from {@link DefaultFilter#performFiltering(CharSequence)} to
178 * {@link DefaultFilter#publishResults(CharSequence, android.widget.Filter.FilterResults)}
179 */
180 private static class DefaultFilterResult {
181 public final List<RecipientEntry> entries;
182 public final LinkedHashMap<Long, List<RecipientEntry>> entryMap;
183 public final List<RecipientEntry> nonAggregatedEntries;
184 public final Set<String> existingDestinations;
185 public final List<DirectorySearchParams> paramsList;
186
187 public DefaultFilterResult(List<RecipientEntry> entries,
188 LinkedHashMap<Long, List<RecipientEntry>> entryMap,
189 List<RecipientEntry> nonAggregatedEntries,
190 Set<String> existingDestinations,
191 List<DirectorySearchParams> paramsList) {
192 this.entries = entries;
193 this.entryMap = entryMap;
194 this.nonAggregatedEntries = nonAggregatedEntries;
195 this.existingDestinations = existingDestinations;
196 this.paramsList = paramsList;
197 }
198 }
199
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700200 /**
201 * An asynchronous filter used for loading two data sets: email rows from the local
202 * contact provider and the list of {@link Directory}'s.
203 */
204 private final class DefaultFilter extends Filter {
205
206 @Override
207 protected FilterResults performFiltering(CharSequence constraint) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700208 if (DEBUG) {
209 Log.d(TAG, "start filtering. constraint: " + constraint + ", thread:"
210 + Thread.currentThread());
211 }
212
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700213 final FilterResults results = new FilterResults();
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700214 Cursor defaultDirectoryCursor = null;
215 Cursor directoryCursor = null;
216
217 if (TextUtils.isEmpty(constraint)) {
Paul Westbrook37726c02012-10-25 10:27:38 -0700218 clearTempEntries();
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700219 // Return empty results.
220 return results;
221 }
222
223 try {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800224 defaultDirectoryCursor = doQuery(constraint, mPreferredMaxResultCount,
225 null /* directoryId */);
Tom Taylor80f4abf2012-04-06 13:37:20 -0700226
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700227 if (defaultDirectoryCursor == null) {
228 if (DEBUG) {
229 Log.w(TAG, "null cursor returned for default Email filter query.");
230 }
231 } else {
232 // These variables will become mEntries, mEntryMap, mNonAggregatedEntries, and
233 // mExistingDestinations. Here we shouldn't use those member variables directly
234 // since this method is run outside the UI thread.
235 final LinkedHashMap<Long, List<RecipientEntry>> entryMap =
236 new LinkedHashMap<Long, List<RecipientEntry>>();
237 final List<RecipientEntry> nonAggregatedEntries =
238 new ArrayList<RecipientEntry>();
239 final Set<String> existingDestinations = new HashSet<String>();
240
241 while (defaultDirectoryCursor.moveToNext()) {
242 // Note: At this point each entry doesn't contain any photo
243 // (thus getPhotoBytes() returns null).
Scott Kennedy514f8a72013-11-13 15:58:08 -0800244 putOneEntry(new TemporaryEntry(defaultDirectoryCursor,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800245 null /* directoryId */),
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700246 true, entryMap, nonAggregatedEntries, existingDestinations);
247 }
248
249 // We'll copy this result to mEntry in publicResults() (run in the UX thread).
Scott Kennedyf7e202d2013-03-06 21:38:10 -0800250 final List<RecipientEntry> entries = constructEntryList(
251 entryMap, nonAggregatedEntries);
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700252
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700253 final List<DirectorySearchParams> paramsList =
254 searchOtherDirectories(existingDestinations);
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700255
256 results.values = new DefaultFilterResult(
257 entries, entryMap, nonAggregatedEntries,
258 existingDestinations, paramsList);
Scott Kennedy2a87fbf2014-09-11 16:56:35 -0700259 results.count = entries.size();
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700260 }
261 } finally {
262 if (defaultDirectoryCursor != null) {
263 defaultDirectoryCursor.close();
264 }
265 if (directoryCursor != null) {
266 directoryCursor.close();
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700267 }
268 }
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700269 return results;
270 }
271
272 @Override
273 protected void publishResults(final CharSequence constraint, FilterResults results) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700274 mCurrentConstraint = constraint;
275
Paul Westbrook37726c02012-10-25 10:27:38 -0700276 clearTempEntries();
277
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700278 if (results.values != null) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700279 DefaultFilterResult defaultFilterResult = (DefaultFilterResult) results.values;
280 mEntryMap = defaultFilterResult.entryMap;
281 mNonAggregatedEntries = defaultFilterResult.nonAggregatedEntries;
282 mExistingDestinations = defaultFilterResult.existingDestinations;
283
Jin Cao31c33ef2014-09-08 10:46:41 -0700284 cacheCurrentEntriesIfNeeded(defaultFilterResult.entries.size(),
285 defaultFilterResult.paramsList == null ? 0 :
286 defaultFilterResult.paramsList.size());
Paul Westbrook37726c02012-10-25 10:27:38 -0700287
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700288 updateEntries(defaultFilterResult.entries);
289
290 // We need to search other remote directories, doing other Filter requests.
291 if (defaultFilterResult.paramsList != null) {
292 final int limit = mPreferredMaxResultCount -
293 defaultFilterResult.existingDestinations.size();
294 startSearchOtherDirectories(constraint, defaultFilterResult.paramsList, limit);
295 }
Scott Kennedyfa7b0fb2014-04-18 14:40:22 -0700296 } else {
297 updateEntries(Collections.<RecipientEntry>emptyList());
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700298 }
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700299 }
300
301 @Override
302 public CharSequence convertResultToString(Object resultValue) {
Mindy Pereiraf621a602011-05-31 10:09:35 -0700303 final RecipientEntry entry = (RecipientEntry)resultValue;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700304 final String displayName = entry.getDisplayName();
305 final String emailAddress = entry.getDestination();
306 if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, emailAddress)) {
307 return emailAddress;
308 } else {
309 return new Rfc822Token(displayName, emailAddress, null).toString();
310 }
311 }
312 }
313
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700314 protected List<DirectorySearchParams> searchOtherDirectories(Set<String> existingDestinations) {
315 // After having local results, check the size of results. If the results are
316 // not enough, we search remote directories, which will take longer time.
317 final int limit = mPreferredMaxResultCount - existingDestinations.size();
318 if (limit > 0) {
319 if (DEBUG) {
320 Log.d(TAG, "More entries should be needed (current: "
321 + existingDestinations.size()
322 + ", remaining limit: " + limit + ") ");
323 }
324 final Cursor directoryCursor = mContentResolver.query(
325 DirectoryListQuery.URI, DirectoryListQuery.PROJECTION,
326 null, null, null);
327 return setupOtherDirectories(mContext, directoryCursor, mAccount);
328 } else {
329 // We don't need to search other directories.
330 return null;
331 }
332 }
333
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700334 /**
335 * An asynchronous filter that performs search in a particular directory.
336 */
Alon Albert76f1f2d2013-07-14 15:32:49 +0300337 protected class DirectoryFilter extends Filter {
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700338 private final DirectorySearchParams mParams;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700339 private int mLimit;
340
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700341 public DirectoryFilter(DirectorySearchParams params) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700342 mParams = params;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700343 }
344
345 public synchronized void setLimit(int limit) {
346 this.mLimit = limit;
347 }
348
349 public synchronized int getLimit() {
350 return this.mLimit;
351 }
352
353 @Override
354 protected FilterResults performFiltering(CharSequence constraint) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700355 if (DEBUG) {
356 Log.d(TAG, "DirectoryFilter#performFiltering. directoryId: " + mParams.directoryId
357 + ", constraint: " + constraint + ", thread: " + Thread.currentThread());
358 }
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700359 final FilterResults results = new FilterResults();
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700360 results.values = null;
361 results.count = 0;
362
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700363 if (!TextUtils.isEmpty(constraint)) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700364 final ArrayList<TemporaryEntry> tempEntries = new ArrayList<TemporaryEntry>();
365
366 Cursor cursor = null;
367 try {
368 // We don't want to pass this Cursor object to UI thread (b/5017608).
369 // Assuming the result should contain fairly small results (at most ~10),
370 // We just copy everything to local structure.
371 cursor = doQuery(constraint, getLimit(), mParams.directoryId);
Tom Taylor80f4abf2012-04-06 13:37:20 -0700372
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700373 if (cursor != null) {
374 while (cursor.moveToNext()) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800375 tempEntries.add(new TemporaryEntry(cursor, mParams.directoryId));
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700376 }
377 }
378 } finally {
379 if (cursor != null) {
380 cursor.close();
381 }
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700382 }
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700383 if (!tempEntries.isEmpty()) {
384 results.values = tempEntries;
Scott Kennedy2a87fbf2014-09-11 16:56:35 -0700385 results.count = tempEntries.size();
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700386 }
387 }
388
389 if (DEBUG) {
390 Log.v(TAG, "finished loading directory \"" + mParams.displayName + "\"" +
391 " with query " + constraint);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700392 }
393
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700394 return results;
395 }
396
397 @Override
398 protected void publishResults(final CharSequence constraint, FilterResults results) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700399 if (DEBUG) {
400 Log.d(TAG, "DirectoryFilter#publishResult. constraint: " + constraint
401 + ", mCurrentConstraint: " + mCurrentConstraint);
402 }
403 mDelayedMessageHandler.removeDelayedLoadMessage();
404 // Check if the received result matches the current constraint
405 // If not - the user must have continued typing after the request was issued, which
406 // means several member variables (like mRemainingDirectoryLoad) are already
407 // overwritten so shouldn't be touched here anymore.
408 if (TextUtils.equals(constraint, mCurrentConstraint)) {
409 if (results.count > 0) {
Andy Huangdfd7e072011-11-18 14:57:32 -0800410 @SuppressWarnings("unchecked")
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700411 final ArrayList<TemporaryEntry> tempEntries =
412 (ArrayList<TemporaryEntry>) results.values;
413
414 for (TemporaryEntry tempEntry : tempEntries) {
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700415 putOneEntry(tempEntry, mParams.directoryId == Directory.DEFAULT);
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700416 }
417 }
418
419 // If there are remaining directories, set up delayed message again.
420 mRemainingDirectoryCount--;
421 if (mRemainingDirectoryCount > 0) {
422 if (DEBUG) {
423 Log.d(TAG, "Resend delayed load message. Current mRemainingDirectoryLoad: "
424 + mRemainingDirectoryCount);
425 }
426 mDelayedMessageHandler.sendDelayedLoadMessage();
427 }
Paul Westbrook37726c02012-10-25 10:27:38 -0700428
429 // If this directory result has some items, or there are no more directories that
430 // we are waiting for, clear the temp results
431 if (results.count > 0 || mRemainingDirectoryCount == 0) {
432 // Clear the temp entries
433 clearTempEntries();
434 }
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700435 }
436
437 // Show the list again without "waiting" message.
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700438 updateEntries(constructEntryList());
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700439 }
440 }
441
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700442 private final Context mContext;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700443 private final ContentResolver mContentResolver;
444 private Account mAccount;
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700445 protected final int mPreferredMaxResultCount;
Kevin Linb10d1c62014-01-24 12:45:00 -0800446 private DropdownChipLayouter mDropdownChipLayouter;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700447
448 /**
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700449 * {@link #mEntries} is responsible for showing every result for this Adapter. To
450 * construct it, we use {@link #mEntryMap}, {@link #mNonAggregatedEntries}, and
451 * {@link #mExistingDestinations}.
452 *
453 * First, each destination (an email address or a phone number) with a valid contactId is
454 * inserted into {@link #mEntryMap} and grouped by the contactId. Destinations without valid
455 * contactId (possible if they aren't in local storage) are stored in
456 * {@link #mNonAggregatedEntries}.
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700457 * Duplicates are removed using {@link #mExistingDestinations}.
458 *
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700459 * After having all results from Cursor objects, all destinations in mEntryMap are copied to
460 * {@link #mEntries}. If the number of destinations is not enough (i.e. less than
461 * {@link #mPreferredMaxResultCount}), destinations in mNonAggregatedEntries are also used.
462 *
463 * These variables are only used in UI thread, thus should not be touched in
464 * performFiltering() methods.
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700465 */
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700466 private LinkedHashMap<Long, List<RecipientEntry>> mEntryMap;
467 private List<RecipientEntry> mNonAggregatedEntries;
468 private Set<String> mExistingDestinations;
469 /** Note: use {@link #updateEntries(List)} to update this variable. */
470 private List<RecipientEntry> mEntries;
Paul Westbrook37726c02012-10-25 10:27:38 -0700471 private List<RecipientEntry> mTempEntries;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700472
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -0700473 /** The number of directories this adapter is waiting for results. */
474 private int mRemainingDirectoryCount;
475
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700476 /**
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700477 * Used to ignore asynchronous queries with a different constraint, which may happen when
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700478 * users type characters quickly.
479 */
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700480 protected CharSequence mCurrentConstraint;
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700481
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700482 /**
483 * Performs all photo querying as well as caching for repeated lookups.
484 */
485 private PhotoManager mPhotoManager;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700486
Daisuke Miyakawad4baa3f2011-05-26 09:24:00 -0700487 /**
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -0700488 * Handler specific for maintaining "Waiting for more contacts" message, which will be shown
489 * when:
490 * - there are directories to be searched
491 * - results from directories are slow to come
492 */
493 private final class DelayedMessageHandler extends Handler {
494 @Override
495 public void handleMessage(Message msg) {
496 if (mRemainingDirectoryCount > 0) {
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700497 updateEntries(constructEntryList());
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -0700498 }
499 }
500
501 public void sendDelayedLoadMessage() {
502 sendMessageDelayed(obtainMessage(MESSAGE_SEARCH_PENDING, 0, 0, null),
503 MESSAGE_SEARCH_PENDING_DELAY);
504 }
505
506 public void removeDelayedLoadMessage() {
507 removeMessages(MESSAGE_SEARCH_PENDING);
508 }
509 }
510
511 private final DelayedMessageHandler mDelayedMessageHandler = new DelayedMessageHandler();
512
mindyp8c474ec2012-12-14 10:58:44 -0800513 private EntriesUpdatedObserver mEntriesUpdatedObserver;
514
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -0700515 /**
Daisuke Miyakawad4baa3f2011-05-26 09:24:00 -0700516 * Constructor for email queries.
517 */
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700518 public BaseRecipientAdapter(Context context) {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700519 this(context, DEFAULT_PREFERRED_MAX_RESULT_COUNT, QUERY_TYPE_EMAIL);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700520 }
521
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700522 public BaseRecipientAdapter(Context context, int preferredMaxResultCount) {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700523 this(context, preferredMaxResultCount, QUERY_TYPE_EMAIL);
524 }
525
526 public BaseRecipientAdapter(int queryMode, Context context) {
527 this(context, DEFAULT_PREFERRED_MAX_RESULT_COUNT, queryMode);
528 }
529
530 public BaseRecipientAdapter(int queryMode, Context context, int preferredMaxResultCount) {
531 this(context, preferredMaxResultCount, queryMode);
532 }
533
534 public BaseRecipientAdapter(Context context, int preferredMaxResultCount, int queryMode) {
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700535 mContext = context;
536 mContentResolver = context.getContentResolver();
537 mPreferredMaxResultCount = preferredMaxResultCount;
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700538 mPhotoManager = new DefaultPhotoManager(mContentResolver);
Tom Taylor80f4abf2012-04-06 13:37:20 -0700539 mQueryType = queryMode;
540
541 if (queryMode == QUERY_TYPE_EMAIL) {
Andrew Sapperstein1db635b2014-04-29 13:07:57 -0700542 mQueryMode = Queries.EMAIL;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700543 } else if (queryMode == QUERY_TYPE_PHONE) {
Andrew Sapperstein1db635b2014-04-29 13:07:57 -0700544 mQueryMode = Queries.PHONE;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700545 } else {
Andrew Sapperstein1db635b2014-04-29 13:07:57 -0700546 mQueryMode = Queries.EMAIL;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700547 Log.e(TAG, "Unsupported query type: " + queryMode);
548 }
549 }
550
Alon Albert76f1f2d2013-07-14 15:32:49 +0300551 public Context getContext() {
552 return mContext;
553 }
554
Tom Taylor80f4abf2012-04-06 13:37:20 -0700555 public int getQueryType() {
556 return mQueryType;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700557 }
558
Kevin Linb10d1c62014-01-24 12:45:00 -0800559 public void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) {
560 mDropdownChipLayouter = dropdownChipLayouter;
Andrew Sapperstein1db635b2014-04-29 13:07:57 -0700561 mDropdownChipLayouter.setQuery(mQueryMode);
Kevin Linb10d1c62014-01-24 12:45:00 -0800562 }
563
564 public DropdownChipLayouter getDropdownChipLayouter() {
565 return mDropdownChipLayouter;
566 }
567
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700568 /**
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700569 * Enables overriding the default photo manager that is used.
570 */
571 public void setPhotoManager(PhotoManager photoManager) {
572 mPhotoManager = photoManager;
573 }
574
575 public PhotoManager getPhotoManager() {
576 return mPhotoManager;
577 }
578
579 /**
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700580 * If true, forces using the {@link com.android.ex.chips.SingleRecipientArrayAdapter}
581 * instead of {@link com.android.ex.chips.RecipientAlternatesAdapter} when
582 * clicking on a chip. Default implementation returns {@code false}.
583 */
584 public boolean forceShowAddress() {
585 return false;
586 }
587
588 /**
589 * Used to replace email addresses with chips. Default behavior
590 * queries the ContactsProvider for contact information about the contact.
591 * Derived classes should override this method if they wish to use a
592 * new data source.
593 * @param inAddresses addresses to query
594 * @param callback callback to return results in case of success or failure
595 */
596 public void getMatchingRecipients(ArrayList<String> inAddresses,
597 RecipientAlternatesAdapter.RecipientMatchCallback callback) {
598 RecipientAlternatesAdapter.getMatchingRecipients(
599 getContext(), this, inAddresses, getAccount(), callback);
600 }
601
602 /**
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700603 * Set the account when known. Causes the search to prioritize contacts from that account.
604 */
Andy Huangdfd7e072011-11-18 14:57:32 -0800605 @Override
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700606 public void setAccount(Account account) {
607 mAccount = account;
608 }
609
610 /** Will be called from {@link AutoCompleteTextView} to prepare auto-complete list. */
611 @Override
612 public Filter getFilter() {
613 return new DefaultFilter();
614 }
615
Alon Albert76f1f2d2013-07-14 15:32:49 +0300616 /**
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700617 * An extension to {@link RecipientAlternatesAdapter#getMatchingRecipients} that allows
Alon Albert76f1f2d2013-07-14 15:32:49 +0300618 * additional sources of contacts to be considered as matching recipients.
619 * @param addresses A set of addresses to be matched
620 * @return A list of matches or null if none found
621 */
622 public Map<String, RecipientEntry> getMatchingRecipients(Set<String> addresses) {
623 return null;
624 }
625
mindyp16923ee2012-12-10 11:58:29 -0800626 public static List<DirectorySearchParams> setupOtherDirectories(Context context,
627 Cursor directoryCursor, Account account) {
628 final PackageManager packageManager = context.getPackageManager();
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700629 final List<DirectorySearchParams> paramsList = new ArrayList<DirectorySearchParams>();
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700630 DirectorySearchParams preferredDirectory = null;
631 while (directoryCursor.moveToNext()) {
632 final long id = directoryCursor.getLong(DirectoryListQuery.ID);
633
634 // Skip the local invisible directory, because the default directory already includes
635 // all local results.
636 if (id == Directory.LOCAL_INVISIBLE) {
637 continue;
638 }
639
640 final DirectorySearchParams params = new DirectorySearchParams();
641 final String packageName = directoryCursor.getString(DirectoryListQuery.PACKAGE_NAME);
642 final int resourceId = directoryCursor.getInt(DirectoryListQuery.TYPE_RESOURCE_ID);
643 params.directoryId = id;
644 params.displayName = directoryCursor.getString(DirectoryListQuery.DISPLAY_NAME);
645 params.accountName = directoryCursor.getString(DirectoryListQuery.ACCOUNT_NAME);
646 params.accountType = directoryCursor.getString(DirectoryListQuery.ACCOUNT_TYPE);
647 if (packageName != null && resourceId != 0) {
648 try {
649 final Resources resources =
650 packageManager.getResourcesForApplication(packageName);
651 params.directoryType = resources.getString(resourceId);
652 if (params.directoryType == null) {
653 Log.e(TAG, "Cannot resolve directory name: "
654 + resourceId + "@" + packageName);
655 }
656 } catch (NameNotFoundException e) {
657 Log.e(TAG, "Cannot resolve directory name: "
658 + resourceId + "@" + packageName, e);
659 }
660 }
661
662 // If an account has been provided and we found a directory that
663 // corresponds to that account, place that directory second, directly
664 // underneath the local contacts.
mindyp16923ee2012-12-10 11:58:29 -0800665 if (account != null && account.name.equals(params.accountName) &&
666 account.type.equals(params.accountType)) {
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700667 preferredDirectory = params;
668 } else {
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700669 paramsList.add(params);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700670 }
671 }
672
673 if (preferredDirectory != null) {
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700674 paramsList.add(1, preferredDirectory);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700675 }
676
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700677 return paramsList;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700678 }
679
680 /**
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700681 * Starts search in other directories using {@link Filter}. Results will be handled in
682 * {@link DirectoryFilter}.
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700683 */
Alon Albert76f1f2d2013-07-14 15:32:49 +0300684 protected void startSearchOtherDirectories(
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700685 CharSequence constraint, List<DirectorySearchParams> paramsList, int limit) {
686 final int count = paramsList.size();
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700687 // Note: skipping the default partition (index 0), which has already been loaded
688 for (int i = 1; i < count; i++) {
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700689 final DirectorySearchParams params = paramsList.get(i);
690 params.constraint = constraint;
691 if (params.filter == null) {
692 params.filter = new DirectoryFilter(params);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700693 }
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700694 params.filter.setLimit(limit);
695 params.filter.filter(constraint);
696 }
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -0700697
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700698 // Directory search started. We may show "waiting" message if directory results are slow
699 // enough.
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -0700700 mRemainingDirectoryCount = count - 1;
701 mDelayedMessageHandler.sendDelayedLoadMessage();
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700702 }
703
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700704 /**
705 * Called whenever {@link com.android.ex.chips.BaseRecipientAdapter.DirectoryFilter}
706 * wants to add an additional entry to the results. Derived classes should override
707 * this method if they are not using the default data structures provided by
708 * {@link com.android.ex.chips.BaseRecipientAdapter} and are instead using their
709 * own data structures to store and collate data.
710 * @param entry the entry being added
711 * @param isAggregatedEntry
712 */
713 protected void putOneEntry(TemporaryEntry entry, boolean isAggregatedEntry) {
714 putOneEntry(entry, isAggregatedEntry,
715 mEntryMap, mNonAggregatedEntries, mExistingDestinations);
716 }
717
Scott Kennedyf7e202d2013-03-06 21:38:10 -0800718 private static void putOneEntry(TemporaryEntry entry, boolean isAggregatedEntry,
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700719 LinkedHashMap<Long, List<RecipientEntry>> entryMap,
720 List<RecipientEntry> nonAggregatedEntries,
721 Set<String> existingDestinations) {
722 if (existingDestinations.contains(entry.destination)) {
723 return;
724 }
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700725
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700726 existingDestinations.add(entry.destination);
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700727
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700728 if (!isAggregatedEntry) {
729 nonAggregatedEntries.add(RecipientEntry.constructTopLevelEntry(
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700730 entry.displayName,
Makoto Onuki00adb322012-05-01 16:50:41 -0700731 entry.displayNameSource,
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700732 entry.destination, entry.destinationType, entry.destinationLabel,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800733 entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
734 true, entry.lookupKey));
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700735 } else if (entryMap.containsKey(entry.contactId)) {
736 // We already have a section for the person.
737 final List<RecipientEntry> entryList = entryMap.get(entry.contactId);
738 entryList.add(RecipientEntry.constructSecondLevelEntry(
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700739 entry.displayName,
Makoto Onuki00adb322012-05-01 16:50:41 -0700740 entry.displayNameSource,
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700741 entry.destination, entry.destinationType, entry.destinationLabel,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800742 entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
743 true, entry.lookupKey));
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700744 } else {
745 final List<RecipientEntry> entryList = new ArrayList<RecipientEntry>();
746 entryList.add(RecipientEntry.constructTopLevelEntry(
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700747 entry.displayName,
Makoto Onuki00adb322012-05-01 16:50:41 -0700748 entry.displayNameSource,
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700749 entry.destination, entry.destinationType, entry.destinationLabel,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800750 entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
751 true, entry.lookupKey));
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700752 entryMap.put(entry.contactId, entryList);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700753 }
754 }
755
756 /**
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700757 * Returns the actual list to use for this Adapter. Derived classes
758 * should override this method if overriding how the adapter stores and collates
759 * data.
760 */
761 protected List<RecipientEntry> constructEntryList() {
762 return constructEntryList(mEntryMap, mNonAggregatedEntries);
763 }
764
765 /**
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700766 * Constructs an actual list for this Adapter using {@link #mEntryMap}. Also tries to
767 * fetch a cached photo for each contact entry (other than separators), or request another
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700768 * thread to get one from directories.
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700769 */
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700770 private List<RecipientEntry> constructEntryList(
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700771 LinkedHashMap<Long, List<RecipientEntry>> entryMap,
Scott Kennedyf7e202d2013-03-06 21:38:10 -0800772 List<RecipientEntry> nonAggregatedEntries) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700773 final List<RecipientEntry> entries = new ArrayList<RecipientEntry>();
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700774 int validEntryCount = 0;
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700775 for (Map.Entry<Long, List<RecipientEntry>> mapEntry : entryMap.entrySet()) {
Mindy Pereiraf621a602011-05-31 10:09:35 -0700776 final List<RecipientEntry> entryList = mapEntry.getValue();
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700777 final int size = entryList.size();
778 for (int i = 0; i < size; i++) {
Mindy Pereiraf621a602011-05-31 10:09:35 -0700779 RecipientEntry entry = entryList.get(i);
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700780 entries.add(entry);
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700781 mPhotoManager.populatePhotoBytesAsync(entry, this);
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700782 validEntryCount++;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700783 }
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700784 if (validEntryCount > mPreferredMaxResultCount) {
785 break;
786 }
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700787 }
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700788 if (validEntryCount <= mPreferredMaxResultCount) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700789 for (RecipientEntry entry : nonAggregatedEntries) {
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700790 if (validEntryCount > mPreferredMaxResultCount) {
791 break;
792 }
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700793 entries.add(entry);
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700794 mPhotoManager.populatePhotoBytesAsync(entry, this);
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700795 validEntryCount++;
796 }
797 }
798
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700799 return entries;
800 }
801
mindyp8c474ec2012-12-14 10:58:44 -0800802
Mike Schneider38fe9842014-03-04 08:14:41 +0100803 public interface EntriesUpdatedObserver {
mindyp8c474ec2012-12-14 10:58:44 -0800804 public void onChanged(List<RecipientEntry> entries);
805 }
806
807 public void registerUpdateObserver(EntriesUpdatedObserver observer) {
808 mEntriesUpdatedObserver = observer;
809 }
810
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700811 /** Resets {@link #mEntries} and notify the event to its parent ListView. */
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700812 protected void updateEntries(List<RecipientEntry> newEntries) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700813 mEntries = newEntries;
mindyp8c474ec2012-12-14 10:58:44 -0800814 mEntriesUpdatedObserver.onChanged(newEntries);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700815 notifyDataSetChanged();
816 }
817
Jin Cao31c33ef2014-09-08 10:46:41 -0700818 /**
819 * If there are no local results and we are searching alternate results,
820 * in the new result set, cache off what had been shown to the user for use until
821 * the first directory result is returned
822 * @param newEntryCount number of newly loaded entries
823 * @param paramListCount number of alternate filters it will search (including the current one).
824 */
825 protected void cacheCurrentEntriesIfNeeded(int newEntryCount, int paramListCount) {
826 if (newEntryCount == 0 && paramListCount > 1) {
827 cacheCurrentEntries();
828 }
829 }
830
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700831 protected void cacheCurrentEntries() {
Paul Westbrook37726c02012-10-25 10:27:38 -0700832 mTempEntries = mEntries;
833 }
834
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700835 protected void clearTempEntries() {
Paul Westbrook37726c02012-10-25 10:27:38 -0700836 mTempEntries = null;
837 }
838
Alon Albert76f1f2d2013-07-14 15:32:49 +0300839 protected List<RecipientEntry> getEntries() {
Paul Westbrook37726c02012-10-25 10:27:38 -0700840 return mTempEntries != null ? mTempEntries : mEntries;
841 }
842
Jin Cao35e82d42014-06-02 17:21:21 -0700843 protected void fetchPhoto(final RecipientEntry entry, PhotoManager.PhotoManagerCallback cb) {
844 mPhotoManager.populatePhotoBytesAsync(entry, cb);
Mindy Pereira6b6de622011-06-07 16:39:24 -0700845 }
846
Daisuke Miyakawad4baa3f2011-05-26 09:24:00 -0700847 private Cursor doQuery(CharSequence constraint, int limit, Long directoryId) {
Andrew Sapperstein1db635b2014-04-29 13:07:57 -0700848 final Uri.Builder builder = mQueryMode.getContentFilterUri().buildUpon()
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700849 .appendPath(constraint.toString())
850 .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
851 String.valueOf(limit + ALLOWANCE_FOR_DUPLICATES));
852 if (directoryId != null) {
853 builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
854 String.valueOf(directoryId));
855 }
856 if (mAccount != null) {
857 builder.appendQueryParameter(PRIMARY_ACCOUNT_NAME, mAccount.name);
858 builder.appendQueryParameter(PRIMARY_ACCOUNT_TYPE, mAccount.type);
859 }
860 final long start = System.currentTimeMillis();
861 final Cursor cursor = mContentResolver.query(
Andrew Sapperstein1db635b2014-04-29 13:07:57 -0700862 builder.build(), mQueryMode.getProjection(), null, null, null);
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700863 final long end = System.currentTimeMillis();
864 if (DEBUG) {
865 Log.d(TAG, "Time for autocomplete (query: " + constraint
866 + ", directoryId: " + directoryId + ", num_of_results: "
867 + (cursor != null ? cursor.getCount() : "null") + "): "
868 + (end - start) + " ms");
Daisuke Miyakawad4baa3f2011-05-26 09:24:00 -0700869 }
870 return cursor;
871 }
872
Daisuke Miyakawacc208802011-07-21 15:10:06 -0700873 // TODO: This won't be used at all. We should find better way to quit the thread..
874 /*public void close() {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700875 mEntries = null;
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700876 mPhotoCacheMap.evictAll();
Daisuke Miyakawacc208802011-07-21 15:10:06 -0700877 if (!sPhotoHandlerThread.quit()) {
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700878 Log.w(TAG, "Failed to quit photo handler thread, ignoring it.");
879 }
Daisuke Miyakawacc208802011-07-21 15:10:06 -0700880 }*/
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700881
882 @Override
883 public int getCount() {
Paul Westbrook37726c02012-10-25 10:27:38 -0700884 final List<RecipientEntry> entries = getEntries();
885 return entries != null ? entries.size() : 0;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700886 }
887
888 @Override
Scott Kennedy858e0942013-10-10 11:50:01 -0700889 public RecipientEntry getItem(int position) {
Paul Westbrook37726c02012-10-25 10:27:38 -0700890 return getEntries().get(position);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700891 }
892
893 @Override
894 public long getItemId(int position) {
895 return position;
896 }
897
898 @Override
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700899 public int getViewTypeCount() {
Mindy Pereiraf621a602011-05-31 10:09:35 -0700900 return RecipientEntry.ENTRY_TYPE_SIZE;
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700901 }
902
903 @Override
904 public int getItemViewType(int position) {
Paul Westbrook37726c02012-10-25 10:27:38 -0700905 return getEntries().get(position).getEntryType();
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700906 }
907
908 @Override
Daisuke Miyakawa6d6bd682011-07-16 17:33:10 -0700909 public boolean isEnabled(int position) {
Paul Westbrook37726c02012-10-25 10:27:38 -0700910 return getEntries().get(position).isSelectable();
Daisuke Miyakawa6d6bd682011-07-16 17:33:10 -0700911 }
912
913 @Override
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700914 public View getView(int position, View convertView, ViewGroup parent) {
Paul Westbrook37726c02012-10-25 10:27:38 -0700915 final RecipientEntry entry = getEntries().get(position);
Makoto Onuki00adb322012-05-01 16:50:41 -0700916
Kevin Lind5ce95b2014-01-27 15:58:40 -0800917 final String constraint = mCurrentConstraint == null ? null :
918 mCurrentConstraint.toString();
919
Kevin Linb10d1c62014-01-24 12:45:00 -0800920 return mDropdownChipLayouter.bindView(convertView, parent, entry, position,
Kevin Lind5ce95b2014-01-27 15:58:40 -0800921 AdapterType.BASE_RECIPIENT, constraint);
Daisuke Miyakawad4baa3f2011-05-26 09:24:00 -0700922 }
mindyp16923ee2012-12-10 11:58:29 -0800923
924 public Account getAccount() {
925 return mAccount;
926 }
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700927
928 @Override
Andrew Sapperstein50429c52014-06-12 15:12:10 -0700929 public void onPhotoBytesPopulated() {
930 // Default implementation does nothing
931 }
932
933 @Override
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700934 public void onPhotoBytesAsynchronouslyPopulated() {
935 notifyDataSetChanged();
936 }
Jin Cao0efdc532014-06-04 15:35:16 -0700937
938 @Override
939 public void onPhotoBytesAsyncLoadFailed() {
Andrew Sapperstein50429c52014-06-12 15:12:10 -0700940 // Default implementation does nothing
Jin Cao0efdc532014-06-04 15:35:16 -0700941 }
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700942}