blob: 070f7ad163da442fec9075a0b518a4fef3c07d5c [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 }
Andrew Sapperstein0437e862014-10-17 17:52:13 -0700324 Cursor directoryCursor = null;
325 try {
326 directoryCursor = mContentResolver.query(
327 DirectoryListQuery.URI, DirectoryListQuery.PROJECTION,
328 null, null, null);
329 return setupOtherDirectories(mContext, directoryCursor, mAccount);
330 } finally {
331 if (directoryCursor != null) {
332 directoryCursor.close();
333 }
334 }
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700335 } else {
336 // We don't need to search other directories.
337 return null;
338 }
339 }
340
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700341 /**
342 * An asynchronous filter that performs search in a particular directory.
343 */
Alon Albert76f1f2d2013-07-14 15:32:49 +0300344 protected class DirectoryFilter extends Filter {
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700345 private final DirectorySearchParams mParams;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700346 private int mLimit;
347
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700348 public DirectoryFilter(DirectorySearchParams params) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700349 mParams = params;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700350 }
351
352 public synchronized void setLimit(int limit) {
353 this.mLimit = limit;
354 }
355
356 public synchronized int getLimit() {
357 return this.mLimit;
358 }
359
360 @Override
361 protected FilterResults performFiltering(CharSequence constraint) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700362 if (DEBUG) {
363 Log.d(TAG, "DirectoryFilter#performFiltering. directoryId: " + mParams.directoryId
364 + ", constraint: " + constraint + ", thread: " + Thread.currentThread());
365 }
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700366 final FilterResults results = new FilterResults();
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700367 results.values = null;
368 results.count = 0;
369
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700370 if (!TextUtils.isEmpty(constraint)) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700371 final ArrayList<TemporaryEntry> tempEntries = new ArrayList<TemporaryEntry>();
372
373 Cursor cursor = null;
374 try {
375 // We don't want to pass this Cursor object to UI thread (b/5017608).
376 // Assuming the result should contain fairly small results (at most ~10),
377 // We just copy everything to local structure.
378 cursor = doQuery(constraint, getLimit(), mParams.directoryId);
Tom Taylor80f4abf2012-04-06 13:37:20 -0700379
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700380 if (cursor != null) {
381 while (cursor.moveToNext()) {
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800382 tempEntries.add(new TemporaryEntry(cursor, mParams.directoryId));
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700383 }
384 }
385 } finally {
386 if (cursor != null) {
387 cursor.close();
388 }
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700389 }
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700390 if (!tempEntries.isEmpty()) {
391 results.values = tempEntries;
Scott Kennedy2a87fbf2014-09-11 16:56:35 -0700392 results.count = tempEntries.size();
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700393 }
394 }
395
396 if (DEBUG) {
397 Log.v(TAG, "finished loading directory \"" + mParams.displayName + "\"" +
398 " with query " + constraint);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700399 }
400
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700401 return results;
402 }
403
404 @Override
405 protected void publishResults(final CharSequence constraint, FilterResults results) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700406 if (DEBUG) {
407 Log.d(TAG, "DirectoryFilter#publishResult. constraint: " + constraint
408 + ", mCurrentConstraint: " + mCurrentConstraint);
409 }
410 mDelayedMessageHandler.removeDelayedLoadMessage();
411 // Check if the received result matches the current constraint
412 // If not - the user must have continued typing after the request was issued, which
413 // means several member variables (like mRemainingDirectoryLoad) are already
414 // overwritten so shouldn't be touched here anymore.
415 if (TextUtils.equals(constraint, mCurrentConstraint)) {
416 if (results.count > 0) {
Andy Huangdfd7e072011-11-18 14:57:32 -0800417 @SuppressWarnings("unchecked")
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700418 final ArrayList<TemporaryEntry> tempEntries =
419 (ArrayList<TemporaryEntry>) results.values;
420
421 for (TemporaryEntry tempEntry : tempEntries) {
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700422 putOneEntry(tempEntry, mParams.directoryId == Directory.DEFAULT);
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700423 }
424 }
425
426 // If there are remaining directories, set up delayed message again.
427 mRemainingDirectoryCount--;
428 if (mRemainingDirectoryCount > 0) {
429 if (DEBUG) {
430 Log.d(TAG, "Resend delayed load message. Current mRemainingDirectoryLoad: "
431 + mRemainingDirectoryCount);
432 }
433 mDelayedMessageHandler.sendDelayedLoadMessage();
434 }
Paul Westbrook37726c02012-10-25 10:27:38 -0700435
436 // If this directory result has some items, or there are no more directories that
437 // we are waiting for, clear the temp results
438 if (results.count > 0 || mRemainingDirectoryCount == 0) {
439 // Clear the temp entries
440 clearTempEntries();
441 }
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700442 }
443
444 // Show the list again without "waiting" message.
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700445 updateEntries(constructEntryList());
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700446 }
447 }
448
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700449 private final Context mContext;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700450 private final ContentResolver mContentResolver;
451 private Account mAccount;
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700452 protected final int mPreferredMaxResultCount;
Kevin Linb10d1c62014-01-24 12:45:00 -0800453 private DropdownChipLayouter mDropdownChipLayouter;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700454
455 /**
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700456 * {@link #mEntries} is responsible for showing every result for this Adapter. To
457 * construct it, we use {@link #mEntryMap}, {@link #mNonAggregatedEntries}, and
458 * {@link #mExistingDestinations}.
459 *
460 * First, each destination (an email address or a phone number) with a valid contactId is
461 * inserted into {@link #mEntryMap} and grouped by the contactId. Destinations without valid
462 * contactId (possible if they aren't in local storage) are stored in
463 * {@link #mNonAggregatedEntries}.
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700464 * Duplicates are removed using {@link #mExistingDestinations}.
465 *
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700466 * After having all results from Cursor objects, all destinations in mEntryMap are copied to
467 * {@link #mEntries}. If the number of destinations is not enough (i.e. less than
468 * {@link #mPreferredMaxResultCount}), destinations in mNonAggregatedEntries are also used.
469 *
470 * These variables are only used in UI thread, thus should not be touched in
471 * performFiltering() methods.
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700472 */
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700473 private LinkedHashMap<Long, List<RecipientEntry>> mEntryMap;
474 private List<RecipientEntry> mNonAggregatedEntries;
475 private Set<String> mExistingDestinations;
476 /** Note: use {@link #updateEntries(List)} to update this variable. */
477 private List<RecipientEntry> mEntries;
Paul Westbrook37726c02012-10-25 10:27:38 -0700478 private List<RecipientEntry> mTempEntries;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700479
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -0700480 /** The number of directories this adapter is waiting for results. */
481 private int mRemainingDirectoryCount;
482
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700483 /**
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700484 * Used to ignore asynchronous queries with a different constraint, which may happen when
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700485 * users type characters quickly.
486 */
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700487 protected CharSequence mCurrentConstraint;
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700488
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700489 /**
490 * Performs all photo querying as well as caching for repeated lookups.
491 */
492 private PhotoManager mPhotoManager;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700493
Daisuke Miyakawad4baa3f2011-05-26 09:24:00 -0700494 /**
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -0700495 * Handler specific for maintaining "Waiting for more contacts" message, which will be shown
496 * when:
497 * - there are directories to be searched
498 * - results from directories are slow to come
499 */
500 private final class DelayedMessageHandler extends Handler {
501 @Override
502 public void handleMessage(Message msg) {
503 if (mRemainingDirectoryCount > 0) {
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700504 updateEntries(constructEntryList());
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -0700505 }
506 }
507
508 public void sendDelayedLoadMessage() {
509 sendMessageDelayed(obtainMessage(MESSAGE_SEARCH_PENDING, 0, 0, null),
510 MESSAGE_SEARCH_PENDING_DELAY);
511 }
512
513 public void removeDelayedLoadMessage() {
514 removeMessages(MESSAGE_SEARCH_PENDING);
515 }
516 }
517
518 private final DelayedMessageHandler mDelayedMessageHandler = new DelayedMessageHandler();
519
mindyp8c474ec2012-12-14 10:58:44 -0800520 private EntriesUpdatedObserver mEntriesUpdatedObserver;
521
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -0700522 /**
Daisuke Miyakawad4baa3f2011-05-26 09:24:00 -0700523 * Constructor for email queries.
524 */
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700525 public BaseRecipientAdapter(Context context) {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700526 this(context, DEFAULT_PREFERRED_MAX_RESULT_COUNT, QUERY_TYPE_EMAIL);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700527 }
528
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700529 public BaseRecipientAdapter(Context context, int preferredMaxResultCount) {
Tom Taylor80f4abf2012-04-06 13:37:20 -0700530 this(context, preferredMaxResultCount, QUERY_TYPE_EMAIL);
531 }
532
533 public BaseRecipientAdapter(int queryMode, Context context) {
534 this(context, DEFAULT_PREFERRED_MAX_RESULT_COUNT, queryMode);
535 }
536
537 public BaseRecipientAdapter(int queryMode, Context context, int preferredMaxResultCount) {
538 this(context, preferredMaxResultCount, queryMode);
539 }
540
541 public BaseRecipientAdapter(Context context, int preferredMaxResultCount, int queryMode) {
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700542 mContext = context;
543 mContentResolver = context.getContentResolver();
544 mPreferredMaxResultCount = preferredMaxResultCount;
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700545 mPhotoManager = new DefaultPhotoManager(mContentResolver);
Tom Taylor80f4abf2012-04-06 13:37:20 -0700546 mQueryType = queryMode;
547
548 if (queryMode == QUERY_TYPE_EMAIL) {
Andrew Sapperstein1db635b2014-04-29 13:07:57 -0700549 mQueryMode = Queries.EMAIL;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700550 } else if (queryMode == QUERY_TYPE_PHONE) {
Andrew Sapperstein1db635b2014-04-29 13:07:57 -0700551 mQueryMode = Queries.PHONE;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700552 } else {
Andrew Sapperstein1db635b2014-04-29 13:07:57 -0700553 mQueryMode = Queries.EMAIL;
Tom Taylor80f4abf2012-04-06 13:37:20 -0700554 Log.e(TAG, "Unsupported query type: " + queryMode);
555 }
556 }
557
Alon Albert76f1f2d2013-07-14 15:32:49 +0300558 public Context getContext() {
559 return mContext;
560 }
561
Tom Taylor80f4abf2012-04-06 13:37:20 -0700562 public int getQueryType() {
563 return mQueryType;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700564 }
565
Kevin Linb10d1c62014-01-24 12:45:00 -0800566 public void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) {
567 mDropdownChipLayouter = dropdownChipLayouter;
Andrew Sapperstein1db635b2014-04-29 13:07:57 -0700568 mDropdownChipLayouter.setQuery(mQueryMode);
Kevin Linb10d1c62014-01-24 12:45:00 -0800569 }
570
571 public DropdownChipLayouter getDropdownChipLayouter() {
572 return mDropdownChipLayouter;
573 }
574
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700575 /**
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700576 * Enables overriding the default photo manager that is used.
577 */
578 public void setPhotoManager(PhotoManager photoManager) {
579 mPhotoManager = photoManager;
580 }
581
582 public PhotoManager getPhotoManager() {
583 return mPhotoManager;
584 }
585
586 /**
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700587 * If true, forces using the {@link com.android.ex.chips.SingleRecipientArrayAdapter}
588 * instead of {@link com.android.ex.chips.RecipientAlternatesAdapter} when
589 * clicking on a chip. Default implementation returns {@code false}.
590 */
591 public boolean forceShowAddress() {
592 return false;
593 }
594
595 /**
596 * Used to replace email addresses with chips. Default behavior
597 * queries the ContactsProvider for contact information about the contact.
598 * Derived classes should override this method if they wish to use a
599 * new data source.
600 * @param inAddresses addresses to query
601 * @param callback callback to return results in case of success or failure
602 */
603 public void getMatchingRecipients(ArrayList<String> inAddresses,
604 RecipientAlternatesAdapter.RecipientMatchCallback callback) {
605 RecipientAlternatesAdapter.getMatchingRecipients(
606 getContext(), this, inAddresses, getAccount(), callback);
607 }
608
609 /**
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700610 * Set the account when known. Causes the search to prioritize contacts from that account.
611 */
Andy Huangdfd7e072011-11-18 14:57:32 -0800612 @Override
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700613 public void setAccount(Account account) {
614 mAccount = account;
615 }
616
617 /** Will be called from {@link AutoCompleteTextView} to prepare auto-complete list. */
618 @Override
619 public Filter getFilter() {
620 return new DefaultFilter();
621 }
622
Alon Albert76f1f2d2013-07-14 15:32:49 +0300623 /**
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700624 * An extension to {@link RecipientAlternatesAdapter#getMatchingRecipients} that allows
Alon Albert76f1f2d2013-07-14 15:32:49 +0300625 * additional sources of contacts to be considered as matching recipients.
626 * @param addresses A set of addresses to be matched
627 * @return A list of matches or null if none found
628 */
629 public Map<String, RecipientEntry> getMatchingRecipients(Set<String> addresses) {
630 return null;
631 }
632
mindyp16923ee2012-12-10 11:58:29 -0800633 public static List<DirectorySearchParams> setupOtherDirectories(Context context,
634 Cursor directoryCursor, Account account) {
635 final PackageManager packageManager = context.getPackageManager();
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700636 final List<DirectorySearchParams> paramsList = new ArrayList<DirectorySearchParams>();
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700637 DirectorySearchParams preferredDirectory = null;
638 while (directoryCursor.moveToNext()) {
639 final long id = directoryCursor.getLong(DirectoryListQuery.ID);
640
641 // Skip the local invisible directory, because the default directory already includes
642 // all local results.
643 if (id == Directory.LOCAL_INVISIBLE) {
644 continue;
645 }
646
647 final DirectorySearchParams params = new DirectorySearchParams();
648 final String packageName = directoryCursor.getString(DirectoryListQuery.PACKAGE_NAME);
649 final int resourceId = directoryCursor.getInt(DirectoryListQuery.TYPE_RESOURCE_ID);
650 params.directoryId = id;
651 params.displayName = directoryCursor.getString(DirectoryListQuery.DISPLAY_NAME);
652 params.accountName = directoryCursor.getString(DirectoryListQuery.ACCOUNT_NAME);
653 params.accountType = directoryCursor.getString(DirectoryListQuery.ACCOUNT_TYPE);
654 if (packageName != null && resourceId != 0) {
655 try {
656 final Resources resources =
657 packageManager.getResourcesForApplication(packageName);
658 params.directoryType = resources.getString(resourceId);
659 if (params.directoryType == null) {
660 Log.e(TAG, "Cannot resolve directory name: "
661 + resourceId + "@" + packageName);
662 }
663 } catch (NameNotFoundException e) {
664 Log.e(TAG, "Cannot resolve directory name: "
665 + resourceId + "@" + packageName, e);
666 }
667 }
668
669 // If an account has been provided and we found a directory that
670 // corresponds to that account, place that directory second, directly
671 // underneath the local contacts.
mindyp16923ee2012-12-10 11:58:29 -0800672 if (account != null && account.name.equals(params.accountName) &&
673 account.type.equals(params.accountType)) {
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700674 preferredDirectory = params;
675 } else {
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700676 paramsList.add(params);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700677 }
678 }
679
680 if (preferredDirectory != null) {
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700681 paramsList.add(1, preferredDirectory);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700682 }
683
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700684 return paramsList;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700685 }
686
687 /**
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700688 * Starts search in other directories using {@link Filter}. Results will be handled in
689 * {@link DirectoryFilter}.
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700690 */
Alon Albert76f1f2d2013-07-14 15:32:49 +0300691 protected void startSearchOtherDirectories(
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700692 CharSequence constraint, List<DirectorySearchParams> paramsList, int limit) {
693 final int count = paramsList.size();
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700694 // Note: skipping the default partition (index 0), which has already been loaded
695 for (int i = 1; i < count; i++) {
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700696 final DirectorySearchParams params = paramsList.get(i);
697 params.constraint = constraint;
698 if (params.filter == null) {
699 params.filter = new DirectoryFilter(params);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700700 }
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700701 params.filter.setLimit(limit);
702 params.filter.filter(constraint);
703 }
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -0700704
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700705 // Directory search started. We may show "waiting" message if directory results are slow
706 // enough.
Daisuke Miyakawa4bb6a342011-07-11 10:28:02 -0700707 mRemainingDirectoryCount = count - 1;
708 mDelayedMessageHandler.sendDelayedLoadMessage();
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700709 }
710
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700711 /**
712 * Called whenever {@link com.android.ex.chips.BaseRecipientAdapter.DirectoryFilter}
713 * wants to add an additional entry to the results. Derived classes should override
714 * this method if they are not using the default data structures provided by
715 * {@link com.android.ex.chips.BaseRecipientAdapter} and are instead using their
716 * own data structures to store and collate data.
717 * @param entry the entry being added
718 * @param isAggregatedEntry
719 */
720 protected void putOneEntry(TemporaryEntry entry, boolean isAggregatedEntry) {
721 putOneEntry(entry, isAggregatedEntry,
722 mEntryMap, mNonAggregatedEntries, mExistingDestinations);
723 }
724
Scott Kennedyf7e202d2013-03-06 21:38:10 -0800725 private static void putOneEntry(TemporaryEntry entry, boolean isAggregatedEntry,
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700726 LinkedHashMap<Long, List<RecipientEntry>> entryMap,
727 List<RecipientEntry> nonAggregatedEntries,
728 Set<String> existingDestinations) {
729 if (existingDestinations.contains(entry.destination)) {
730 return;
731 }
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700732
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700733 existingDestinations.add(entry.destination);
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700734
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700735 if (!isAggregatedEntry) {
736 nonAggregatedEntries.add(RecipientEntry.constructTopLevelEntry(
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700737 entry.displayName,
Makoto Onuki00adb322012-05-01 16:50:41 -0700738 entry.displayNameSource,
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700739 entry.destination, entry.destinationType, entry.destinationLabel,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800740 entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
741 true, entry.lookupKey));
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700742 } else if (entryMap.containsKey(entry.contactId)) {
743 // We already have a section for the person.
744 final List<RecipientEntry> entryList = entryMap.get(entry.contactId);
745 entryList.add(RecipientEntry.constructSecondLevelEntry(
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700746 entry.displayName,
Makoto Onuki00adb322012-05-01 16:50:41 -0700747 entry.displayNameSource,
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700748 entry.destination, entry.destinationType, entry.destinationLabel,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800749 entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
750 true, entry.lookupKey));
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700751 } else {
752 final List<RecipientEntry> entryList = new ArrayList<RecipientEntry>();
753 entryList.add(RecipientEntry.constructTopLevelEntry(
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700754 entry.displayName,
Makoto Onuki00adb322012-05-01 16:50:41 -0700755 entry.displayNameSource,
Daisuke Miyakawa72117472011-07-20 14:10:10 -0700756 entry.destination, entry.destinationType, entry.destinationLabel,
Scott Kennedy7a4e6772013-11-21 14:31:33 -0800757 entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
758 true, entry.lookupKey));
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700759 entryMap.put(entry.contactId, entryList);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700760 }
761 }
762
763 /**
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700764 * Returns the actual list to use for this Adapter. Derived classes
765 * should override this method if overriding how the adapter stores and collates
766 * data.
767 */
768 protected List<RecipientEntry> constructEntryList() {
769 return constructEntryList(mEntryMap, mNonAggregatedEntries);
770 }
771
772 /**
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700773 * Constructs an actual list for this Adapter using {@link #mEntryMap}. Also tries to
774 * fetch a cached photo for each contact entry (other than separators), or request another
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700775 * thread to get one from directories.
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700776 */
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700777 private List<RecipientEntry> constructEntryList(
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700778 LinkedHashMap<Long, List<RecipientEntry>> entryMap,
Scott Kennedyf7e202d2013-03-06 21:38:10 -0800779 List<RecipientEntry> nonAggregatedEntries) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700780 final List<RecipientEntry> entries = new ArrayList<RecipientEntry>();
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700781 int validEntryCount = 0;
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700782 for (Map.Entry<Long, List<RecipientEntry>> mapEntry : entryMap.entrySet()) {
Mindy Pereiraf621a602011-05-31 10:09:35 -0700783 final List<RecipientEntry> entryList = mapEntry.getValue();
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700784 final int size = entryList.size();
785 for (int i = 0; i < size; i++) {
Mindy Pereiraf621a602011-05-31 10:09:35 -0700786 RecipientEntry entry = entryList.get(i);
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700787 entries.add(entry);
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700788 mPhotoManager.populatePhotoBytesAsync(entry, this);
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700789 validEntryCount++;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700790 }
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700791 if (validEntryCount > mPreferredMaxResultCount) {
792 break;
793 }
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700794 }
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700795 if (validEntryCount <= mPreferredMaxResultCount) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700796 for (RecipientEntry entry : nonAggregatedEntries) {
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700797 if (validEntryCount > mPreferredMaxResultCount) {
798 break;
799 }
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700800 entries.add(entry);
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700801 mPhotoManager.populatePhotoBytesAsync(entry, this);
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700802 validEntryCount++;
803 }
804 }
805
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700806 return entries;
807 }
808
mindyp8c474ec2012-12-14 10:58:44 -0800809
Mike Schneider38fe9842014-03-04 08:14:41 +0100810 public interface EntriesUpdatedObserver {
mindyp8c474ec2012-12-14 10:58:44 -0800811 public void onChanged(List<RecipientEntry> entries);
812 }
813
814 public void registerUpdateObserver(EntriesUpdatedObserver observer) {
815 mEntriesUpdatedObserver = observer;
816 }
817
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700818 /** Resets {@link #mEntries} and notify the event to its parent ListView. */
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700819 protected void updateEntries(List<RecipientEntry> newEntries) {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700820 mEntries = newEntries;
mindyp8c474ec2012-12-14 10:58:44 -0800821 mEntriesUpdatedObserver.onChanged(newEntries);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700822 notifyDataSetChanged();
823 }
824
Jin Cao31c33ef2014-09-08 10:46:41 -0700825 /**
826 * If there are no local results and we are searching alternate results,
827 * in the new result set, cache off what had been shown to the user for use until
828 * the first directory result is returned
829 * @param newEntryCount number of newly loaded entries
830 * @param paramListCount number of alternate filters it will search (including the current one).
831 */
832 protected void cacheCurrentEntriesIfNeeded(int newEntryCount, int paramListCount) {
833 if (newEntryCount == 0 && paramListCount > 1) {
834 cacheCurrentEntries();
835 }
836 }
837
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700838 protected void cacheCurrentEntries() {
Paul Westbrook37726c02012-10-25 10:27:38 -0700839 mTempEntries = mEntries;
840 }
841
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700842 protected void clearTempEntries() {
Paul Westbrook37726c02012-10-25 10:27:38 -0700843 mTempEntries = null;
844 }
845
Alon Albert76f1f2d2013-07-14 15:32:49 +0300846 protected List<RecipientEntry> getEntries() {
Paul Westbrook37726c02012-10-25 10:27:38 -0700847 return mTempEntries != null ? mTempEntries : mEntries;
848 }
849
Jin Cao35e82d42014-06-02 17:21:21 -0700850 protected void fetchPhoto(final RecipientEntry entry, PhotoManager.PhotoManagerCallback cb) {
851 mPhotoManager.populatePhotoBytesAsync(entry, cb);
Mindy Pereira6b6de622011-06-07 16:39:24 -0700852 }
853
Daisuke Miyakawad4baa3f2011-05-26 09:24:00 -0700854 private Cursor doQuery(CharSequence constraint, int limit, Long directoryId) {
Andrew Sapperstein1db635b2014-04-29 13:07:57 -0700855 final Uri.Builder builder = mQueryMode.getContentFilterUri().buildUpon()
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700856 .appendPath(constraint.toString())
857 .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
858 String.valueOf(limit + ALLOWANCE_FOR_DUPLICATES));
859 if (directoryId != null) {
860 builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
861 String.valueOf(directoryId));
862 }
863 if (mAccount != null) {
864 builder.appendQueryParameter(PRIMARY_ACCOUNT_NAME, mAccount.name);
865 builder.appendQueryParameter(PRIMARY_ACCOUNT_TYPE, mAccount.type);
866 }
867 final long start = System.currentTimeMillis();
868 final Cursor cursor = mContentResolver.query(
Andrew Sapperstein1db635b2014-04-29 13:07:57 -0700869 builder.build(), mQueryMode.getProjection(), null, null, null);
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700870 final long end = System.currentTimeMillis();
871 if (DEBUG) {
872 Log.d(TAG, "Time for autocomplete (query: " + constraint
873 + ", directoryId: " + directoryId + ", num_of_results: "
874 + (cursor != null ? cursor.getCount() : "null") + "): "
875 + (end - start) + " ms");
Daisuke Miyakawad4baa3f2011-05-26 09:24:00 -0700876 }
877 return cursor;
878 }
879
Daisuke Miyakawacc208802011-07-21 15:10:06 -0700880 // TODO: This won't be used at all. We should find better way to quit the thread..
881 /*public void close() {
Daisuke Miyakawa8383f442011-07-05 13:58:29 -0700882 mEntries = null;
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700883 mPhotoCacheMap.evictAll();
Daisuke Miyakawacc208802011-07-21 15:10:06 -0700884 if (!sPhotoHandlerThread.quit()) {
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700885 Log.w(TAG, "Failed to quit photo handler thread, ignoring it.");
886 }
Daisuke Miyakawacc208802011-07-21 15:10:06 -0700887 }*/
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700888
889 @Override
890 public int getCount() {
Paul Westbrook37726c02012-10-25 10:27:38 -0700891 final List<RecipientEntry> entries = getEntries();
892 return entries != null ? entries.size() : 0;
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700893 }
894
895 @Override
Scott Kennedy858e0942013-10-10 11:50:01 -0700896 public RecipientEntry getItem(int position) {
Paul Westbrook37726c02012-10-25 10:27:38 -0700897 return getEntries().get(position);
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700898 }
899
900 @Override
901 public long getItemId(int position) {
902 return position;
903 }
904
905 @Override
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700906 public int getViewTypeCount() {
Mindy Pereiraf621a602011-05-31 10:09:35 -0700907 return RecipientEntry.ENTRY_TYPE_SIZE;
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700908 }
909
910 @Override
911 public int getItemViewType(int position) {
Paul Westbrook37726c02012-10-25 10:27:38 -0700912 return getEntries().get(position).getEntryType();
Daisuke Miyakawa74a977c2011-05-24 17:55:17 -0700913 }
914
915 @Override
Daisuke Miyakawa6d6bd682011-07-16 17:33:10 -0700916 public boolean isEnabled(int position) {
Paul Westbrook37726c02012-10-25 10:27:38 -0700917 return getEntries().get(position).isSelectable();
Daisuke Miyakawa6d6bd682011-07-16 17:33:10 -0700918 }
919
920 @Override
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700921 public View getView(int position, View convertView, ViewGroup parent) {
Paul Westbrook37726c02012-10-25 10:27:38 -0700922 final RecipientEntry entry = getEntries().get(position);
Makoto Onuki00adb322012-05-01 16:50:41 -0700923
Kevin Lind5ce95b2014-01-27 15:58:40 -0800924 final String constraint = mCurrentConstraint == null ? null :
925 mCurrentConstraint.toString();
926
Kevin Linb10d1c62014-01-24 12:45:00 -0800927 return mDropdownChipLayouter.bindView(convertView, parent, entry, position,
Kevin Lind5ce95b2014-01-27 15:58:40 -0800928 AdapterType.BASE_RECIPIENT, constraint);
Daisuke Miyakawad4baa3f2011-05-26 09:24:00 -0700929 }
mindyp16923ee2012-12-10 11:58:29 -0800930
931 public Account getAccount() {
932 return mAccount;
933 }
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700934
935 @Override
Andrew Sapperstein50429c52014-06-12 15:12:10 -0700936 public void onPhotoBytesPopulated() {
937 // Default implementation does nothing
938 }
939
940 @Override
Andrew Sapperstein8af0d3b2014-05-02 15:23:19 -0700941 public void onPhotoBytesAsynchronouslyPopulated() {
942 notifyDataSetChanged();
943 }
Jin Cao0efdc532014-06-04 15:35:16 -0700944
945 @Override
946 public void onPhotoBytesAsyncLoadFailed() {
Andrew Sapperstein50429c52014-06-12 15:12:10 -0700947 // Default implementation does nothing
Jin Cao0efdc532014-06-04 15:35:16 -0700948 }
Daisuke Miyakawa6ac7d0c2011-05-20 14:15:27 -0700949}