blob: b508ddae14cfbce27f0bbeedce856eff82824a7f [file] [log] [blame]
Chiao Cheng89437e82012-11-01 13:41:51 -07001/*
2 * Copyright (C) 2010 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 */
Gary Mai69c182a2016-12-05 13:07:03 -080016package com.android.contacts.list;
Chiao Cheng89437e82012-11-01 13:41:51 -070017
18import android.content.Context;
19import android.content.CursorLoader;
Nancy Chene20d1ac2014-08-15 12:19:29 -070020import android.content.res.Resources;
Chiao Cheng89437e82012-11-01 13:41:51 -070021import android.database.Cursor;
22import android.net.Uri;
23import android.os.Bundle;
24import android.provider.ContactsContract;
Brian Attwellbfd3c472015-02-25 22:33:02 -080025import android.provider.ContactsContract.CommonDataKinds.Phone;
Chiao Cheng89437e82012-11-01 13:41:51 -070026import android.provider.ContactsContract.Contacts;
27import android.provider.ContactsContract.Directory;
28import android.text.TextUtils;
29import android.util.Log;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.ViewGroup;
33import android.widget.QuickContactBadge;
34import android.widget.SectionIndexer;
35import android.widget.TextView;
36
Gary Mai0a49afa2016-12-05 15:53:58 -080037import com.android.contacts.ContactPhotoManager;
38import com.android.contacts.ContactPhotoManager.DefaultImageRequest;
39import com.android.contacts.ContactsUtils;
Arthur Wang3f6a2442016-12-05 14:51:59 -080040import com.android.contacts.R;
Gary Mai69c182a2016-12-05 13:07:03 -080041import com.android.contacts.compat.CompatUtils;
42import com.android.contacts.compat.DirectoryCompat;
43import com.android.contacts.util.SearchUtil;
Chiao Cheng89437e82012-11-01 13:41:51 -070044
45import java.util.HashSet;
46
47/**
48 * Common base class for various contact-related lists, e.g. contact list, phone number list
49 * etc.
50 */
51public abstract class ContactEntryListAdapter extends IndexerListAdapter {
52
53 private static final String TAG = "ContactEntryListAdapter";
54
55 /**
56 * Indicates whether the {@link Directory#LOCAL_INVISIBLE} directory should
57 * be included in the search.
58 */
59 public static final boolean LOCAL_INVISIBLE_DIRECTORY_ENABLED = false;
60
61 private int mDisplayOrder;
62 private int mSortOrder;
63
64 private boolean mDisplayPhotos;
Yorke Leec4a2a232014-04-28 17:53:42 -070065 private boolean mCircularPhotos = true;
Chiao Cheng89437e82012-11-01 13:41:51 -070066 private boolean mQuickContactEnabled;
Andrew Lee4683e542014-06-09 16:24:10 -070067 private boolean mAdjustSelectionBoundsEnabled;
Chiao Cheng89437e82012-11-01 13:41:51 -070068
69 /**
Wenyi Wang25774d22016-04-08 11:15:11 -070070 * indicates if contact queries include favorites
71 */
72 private boolean mIncludeFavorites;
73
Wenyi Wangc9ad9b12016-05-20 15:10:12 -070074 private int mNumberOfFavorites;
75
Wenyi Wang25774d22016-04-08 11:15:11 -070076 /**
Brian Attwellb92b6372014-07-21 23:39:35 -070077 * The root view of the fragment that this adapter is associated with.
78 */
79 private View mFragmentRootView;
80
Chiao Cheng89437e82012-11-01 13:41:51 -070081 private ContactPhotoManager mPhotoLoader;
82
83 private String mQueryString;
Chiao Chenga1554ef2012-12-21 15:45:54 -080084 private String mUpperCaseQueryString;
Chiao Cheng89437e82012-11-01 13:41:51 -070085 private boolean mSearchMode;
86 private int mDirectorySearchMode;
87 private int mDirectoryResultLimit = Integer.MAX_VALUE;
88
Chiao Cheng89437e82012-11-01 13:41:51 -070089 private boolean mEmptyListEnabled = true;
90
91 private boolean mSelectionVisible;
92
93 private ContactListFilter mFilter;
Chiao Cheng89437e82012-11-01 13:41:51 -070094 private boolean mDarkTheme = false;
95
96 /** Resource used to provide header-text for default filter. */
97 private CharSequence mDefaultFilterHeaderText;
98
99 public ContactEntryListAdapter(Context context) {
100 super(context);
Chiao Cheng89437e82012-11-01 13:41:51 -0700101 setDefaultFilterHeaderText(R.string.local_search_label);
Yorke Leed9b54202013-09-11 12:03:41 -0700102 addPartitions();
Chiao Cheng89437e82012-11-01 13:41:51 -0700103 }
104
Brian Attwellb92b6372014-07-21 23:39:35 -0700105 /**
106 * @param fragmentRootView Root view of the fragment. This is used to restrict the scope of
107 * image loading requests that get cancelled on cursor changes.
108 */
109 protected void setFragmentRootView(View fragmentRootView) {
110 mFragmentRootView = fragmentRootView;
111 }
112
Chiao Cheng89437e82012-11-01 13:41:51 -0700113 protected void setDefaultFilterHeaderText(int resourceId) {
114 mDefaultFilterHeaderText = getContext().getResources().getText(resourceId);
115 }
116
117 @Override
Andrew Lee020ba622014-04-25 15:26:35 -0700118 protected ContactListItemView newView(
119 Context context, int partition, Cursor cursor, int position, ViewGroup parent) {
120 final ContactListItemView view = new ContactListItemView(context, null);
121 view.setIsSectionHeaderEnabled(isSectionHeaderDisplayEnabled());
Andrew Lee4683e542014-06-09 16:24:10 -0700122 view.setAdjustSelectionBoundsEnabled(isAdjustSelectionBoundsEnabled());
Andrew Lee020ba622014-04-25 15:26:35 -0700123 return view;
124 }
125
126 @Override
Brian Attwella5ad5572014-09-15 11:18:03 -0700127 protected void bindView(View itemView, int partition, Cursor cursor, int position) {
128 final ContactListItemView view = (ContactListItemView) itemView;
129 view.setIsSectionHeaderEnabled(isSectionHeaderDisplayEnabled());
Tony Mak6473f8f2016-02-23 20:39:16 +0000130 bindWorkProfileIcon(view, partition);
Brian Attwella5ad5572014-09-15 11:18:03 -0700131 }
132
133 @Override
Chiao Cheng89437e82012-11-01 13:41:51 -0700134 protected View createPinnedSectionHeaderView(Context context, ViewGroup parent) {
Yorke Lee525f5132014-07-30 18:59:28 -0700135 return new ContactListPinnedHeaderView(context, null, parent);
Chiao Cheng89437e82012-11-01 13:41:51 -0700136 }
137
138 @Override
139 protected void setPinnedSectionTitle(View pinnedHeaderView, String title) {
Andrew Lee020ba622014-04-25 15:26:35 -0700140 ((ContactListPinnedHeaderView) pinnedHeaderView).setSectionHeaderTitle(title);
Chiao Cheng89437e82012-11-01 13:41:51 -0700141 }
142
Chiao Cheng89437e82012-11-01 13:41:51 -0700143 protected void addPartitions() {
144 addPartition(createDefaultDirectoryPartition());
145 }
146
147 protected DirectoryPartition createDefaultDirectoryPartition() {
148 DirectoryPartition partition = new DirectoryPartition(true, true);
149 partition.setDirectoryId(Directory.DEFAULT);
150 partition.setDirectoryType(getContext().getString(R.string.contactsList));
151 partition.setPriorityDirectory(true);
152 partition.setPhotoSupported(true);
Yorke Leed9b54202013-09-11 12:03:41 -0700153 partition.setLabel(mDefaultFilterHeaderText.toString());
Chiao Cheng89437e82012-11-01 13:41:51 -0700154 return partition;
155 }
156
157 /**
158 * Remove all directories after the default directory. This is typically used when contacts
159 * list screens are asked to exit the search mode and thus need to remove all remote directory
160 * results for the search.
161 *
162 * This code assumes that the default directory and directories before that should not be
163 * deleted (e.g. Join screen has "suggested contacts" directory before the default director,
164 * and we should not remove the directory).
165 */
166 public void removeDirectoriesAfterDefault() {
167 final int partitionCount = getPartitionCount();
168 for (int i = partitionCount - 1; i >= 0; i--) {
169 final Partition partition = getPartition(i);
170 if ((partition instanceof DirectoryPartition)
171 && ((DirectoryPartition) partition).getDirectoryId() == Directory.DEFAULT) {
172 break;
173 } else {
174 removePartition(i);
175 }
176 }
177 }
178
Ben Gilad03a533a2013-08-21 09:46:00 -0700179 protected int getPartitionByDirectoryId(long id) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700180 int count = getPartitionCount();
181 for (int i = 0; i < count; i++) {
182 Partition partition = getPartition(i);
183 if (partition instanceof DirectoryPartition) {
184 if (((DirectoryPartition)partition).getDirectoryId() == id) {
185 return i;
186 }
187 }
188 }
189 return -1;
190 }
191
Ben Gilad03a533a2013-08-21 09:46:00 -0700192 protected DirectoryPartition getDirectoryById(long id) {
193 int count = getPartitionCount();
194 for (int i = 0; i < count; i++) {
195 Partition partition = getPartition(i);
196 if (partition instanceof DirectoryPartition) {
197 final DirectoryPartition directoryPartition = (DirectoryPartition) partition;
198 if (directoryPartition.getDirectoryId() == id) {
199 return directoryPartition;
200 }
201 }
202 }
203 return null;
204 }
205
Chiao Cheng89437e82012-11-01 13:41:51 -0700206 public abstract String getContactDisplayName(int position);
207 public abstract void configureLoader(CursorLoader loader, long directoryId);
208
209 /**
210 * Marks all partitions as "loading"
211 */
212 public void onDataReload() {
213 boolean notify = false;
214 int count = getPartitionCount();
215 for (int i = 0; i < count; i++) {
216 Partition partition = getPartition(i);
217 if (partition instanceof DirectoryPartition) {
218 DirectoryPartition directoryPartition = (DirectoryPartition)partition;
219 if (!directoryPartition.isLoading()) {
220 notify = true;
221 }
222 directoryPartition.setStatus(DirectoryPartition.STATUS_NOT_LOADED);
223 }
224 }
225 if (notify) {
226 notifyDataSetChanged();
227 }
228 }
229
230 @Override
231 public void clearPartitions() {
232 int count = getPartitionCount();
233 for (int i = 0; i < count; i++) {
234 Partition partition = getPartition(i);
235 if (partition instanceof DirectoryPartition) {
236 DirectoryPartition directoryPartition = (DirectoryPartition)partition;
237 directoryPartition.setStatus(DirectoryPartition.STATUS_NOT_LOADED);
238 }
239 }
240 super.clearPartitions();
241 }
242
243 public boolean isSearchMode() {
244 return mSearchMode;
245 }
246
247 public void setSearchMode(boolean flag) {
248 mSearchMode = flag;
249 }
250
251 public String getQueryString() {
252 return mQueryString;
253 }
254
255 public void setQueryString(String queryString) {
256 mQueryString = queryString;
257 if (TextUtils.isEmpty(queryString)) {
258 mUpperCaseQueryString = null;
259 } else {
Chiao Chengecba27e2012-12-27 17:14:53 -0800260 mUpperCaseQueryString = SearchUtil
261 .cleanStartAndEndOfSearchQuery(queryString.toUpperCase()) ;
Chiao Cheng89437e82012-11-01 13:41:51 -0700262 }
263 }
264
Chiao Chenga1554ef2012-12-21 15:45:54 -0800265 public String getUpperCaseQueryString() {
Chiao Cheng89437e82012-11-01 13:41:51 -0700266 return mUpperCaseQueryString;
267 }
268
269 public int getDirectorySearchMode() {
270 return mDirectorySearchMode;
271 }
272
273 public void setDirectorySearchMode(int mode) {
274 mDirectorySearchMode = mode;
275 }
276
277 public int getDirectoryResultLimit() {
278 return mDirectoryResultLimit;
279 }
280
Ben Gilad03a533a2013-08-21 09:46:00 -0700281 public int getDirectoryResultLimit(DirectoryPartition directoryPartition) {
282 final int limit = directoryPartition.getResultLimit();
283 return limit == DirectoryPartition.RESULT_LIMIT_DEFAULT ? mDirectoryResultLimit : limit;
284 }
285
Chiao Cheng89437e82012-11-01 13:41:51 -0700286 public void setDirectoryResultLimit(int limit) {
287 this.mDirectoryResultLimit = limit;
288 }
289
290 public int getContactNameDisplayOrder() {
291 return mDisplayOrder;
292 }
293
294 public void setContactNameDisplayOrder(int displayOrder) {
295 mDisplayOrder = displayOrder;
296 }
297
298 public int getSortOrder() {
299 return mSortOrder;
300 }
301
302 public void setSortOrder(int sortOrder) {
303 mSortOrder = sortOrder;
304 }
305
306 public void setPhotoLoader(ContactPhotoManager photoLoader) {
307 mPhotoLoader = photoLoader;
308 }
309
310 protected ContactPhotoManager getPhotoLoader() {
311 return mPhotoLoader;
312 }
313
314 public boolean getDisplayPhotos() {
315 return mDisplayPhotos;
316 }
317
318 public void setDisplayPhotos(boolean displayPhotos) {
319 mDisplayPhotos = displayPhotos;
320 }
321
Yorke Leec4a2a232014-04-28 17:53:42 -0700322 public boolean getCircularPhotos() {
323 return mCircularPhotos;
324 }
325
326 public void setCircularPhotos(boolean circularPhotos) {
327 mCircularPhotos = circularPhotos;
328 }
329
Chiao Cheng89437e82012-11-01 13:41:51 -0700330 public boolean isEmptyListEnabled() {
331 return mEmptyListEnabled;
332 }
333
334 public void setEmptyListEnabled(boolean flag) {
335 mEmptyListEnabled = flag;
336 }
337
338 public boolean isSelectionVisible() {
339 return mSelectionVisible;
340 }
341
342 public void setSelectionVisible(boolean flag) {
343 this.mSelectionVisible = flag;
344 }
345
346 public boolean isQuickContactEnabled() {
347 return mQuickContactEnabled;
348 }
349
350 public void setQuickContactEnabled(boolean quickContactEnabled) {
351 mQuickContactEnabled = quickContactEnabled;
352 }
353
Andrew Lee4683e542014-06-09 16:24:10 -0700354 public boolean isAdjustSelectionBoundsEnabled() {
355 return mAdjustSelectionBoundsEnabled;
356 }
357
358 public void setAdjustSelectionBoundsEnabled(boolean enabled) {
359 mAdjustSelectionBoundsEnabled = enabled;
360 }
361
Wenyi Wang25774d22016-04-08 11:15:11 -0700362 public boolean shouldIncludeFavorites() {
363 return mIncludeFavorites;
364 }
365
366 public void setIncludeFavorites(boolean includeFavorites) {
367 mIncludeFavorites = includeFavorites;
368 }
369
Wenyi Wang25774d22016-04-08 11:15:11 -0700370 public void setFavoritesSectionHeader(int numberOfFavorites) {
371 if (mIncludeFavorites) {
Wenyi Wangc9ad9b12016-05-20 15:10:12 -0700372 mNumberOfFavorites = numberOfFavorites;
John Shaoda188712016-08-19 14:57:11 -0700373 setSectionHeader(numberOfFavorites);
Wenyi Wang25774d22016-04-08 11:15:11 -0700374 }
375 }
376
Wenyi Wangc9ad9b12016-05-20 15:10:12 -0700377 public int getNumberOfFavorites() {
Tingting Wang54205a82016-07-20 14:27:18 -0700378 return mNumberOfFavorites;
Wenyi Wangc9ad9b12016-05-20 15:10:12 -0700379 }
380
John Shaoda188712016-08-19 14:57:11 -0700381 private void setSectionHeader(int numberOfItems) {
Wenyi Wang25774d22016-04-08 11:15:11 -0700382 SectionIndexer indexer = getIndexer();
383 if (indexer != null) {
John Shaoda188712016-08-19 14:57:11 -0700384 ((ContactsSectionIndexer) indexer).setFavoritesHeader(numberOfItems);
Chiao Cheng89437e82012-11-01 13:41:51 -0700385 }
386 }
387
Chiao Cheng89437e82012-11-01 13:41:51 -0700388 public void setDarkTheme(boolean value) {
389 mDarkTheme = value;
390 }
391
392 /**
393 * Updates partitions according to the directory meta-data contained in the supplied
394 * cursor.
395 */
396 public void changeDirectories(Cursor cursor) {
397 if (cursor.getCount() == 0) {
398 // Directory table must have at least local directory, without which this adapter will
399 // enter very weird state.
400 Log.e(TAG, "Directory search loader returned an empty cursor, which implies we have " +
401 "no directory entries.", new RuntimeException());
402 return;
403 }
404 HashSet<Long> directoryIds = new HashSet<Long>();
405
406 int idColumnIndex = cursor.getColumnIndex(Directory._ID);
407 int directoryTypeColumnIndex = cursor.getColumnIndex(DirectoryListLoader.DIRECTORY_TYPE);
408 int displayNameColumnIndex = cursor.getColumnIndex(Directory.DISPLAY_NAME);
409 int photoSupportColumnIndex = cursor.getColumnIndex(Directory.PHOTO_SUPPORT);
410
411 // TODO preserve the order of partition to match those of the cursor
412 // Phase I: add new directories
413 cursor.moveToPosition(-1);
414 while (cursor.moveToNext()) {
415 long id = cursor.getLong(idColumnIndex);
416 directoryIds.add(id);
417 if (getPartitionByDirectoryId(id) == -1) {
418 DirectoryPartition partition = new DirectoryPartition(false, true);
419 partition.setDirectoryId(id);
Ricky Waif600f072016-04-25 12:13:09 +0100420 if (DirectoryCompat.isRemoteDirectoryId(id)) {
Victor Chang3e21eef2015-12-24 13:17:42 +0800421 if (DirectoryCompat.isEnterpriseDirectoryId(id)) {
422 partition.setLabel(mContext.getString(R.string.directory_search_label_work));
423 } else {
424 partition.setLabel(mContext.getString(R.string.directory_search_label));
425 }
Alon Albertdd1dcc62013-08-28 14:02:47 -0700426 } else {
Victor Chang3e21eef2015-12-24 13:17:42 +0800427 if (DirectoryCompat.isEnterpriseDirectoryId(id)) {
428 partition.setLabel(mContext.getString(R.string.list_filter_phones_work));
429 } else {
430 partition.setLabel(mDefaultFilterHeaderText.toString());
431 }
Alon Albertdd1dcc62013-08-28 14:02:47 -0700432 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700433 partition.setDirectoryType(cursor.getString(directoryTypeColumnIndex));
434 partition.setDisplayName(cursor.getString(displayNameColumnIndex));
435 int photoSupport = cursor.getInt(photoSupportColumnIndex);
436 partition.setPhotoSupported(photoSupport == Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY
437 || photoSupport == Directory.PHOTO_SUPPORT_FULL);
438 addPartition(partition);
439 }
440 }
441
442 // Phase II: remove deleted directories
443 int count = getPartitionCount();
444 for (int i = count; --i >= 0; ) {
445 Partition partition = getPartition(i);
446 if (partition instanceof DirectoryPartition) {
447 long id = ((DirectoryPartition)partition).getDirectoryId();
448 if (!directoryIds.contains(id)) {
449 removePartition(i);
450 }
451 }
452 }
453
454 invalidate();
455 notifyDataSetChanged();
456 }
457
458 @Override
459 public void changeCursor(int partitionIndex, Cursor cursor) {
460 if (partitionIndex >= getPartitionCount()) {
461 // There is no partition for this data
462 return;
463 }
464
465 Partition partition = getPartition(partitionIndex);
466 if (partition instanceof DirectoryPartition) {
467 ((DirectoryPartition)partition).setStatus(DirectoryPartition.STATUS_LOADED);
468 }
469
470 if (mDisplayPhotos && mPhotoLoader != null && isPhotoSupported(partitionIndex)) {
471 mPhotoLoader.refreshCache();
472 }
473
474 super.changeCursor(partitionIndex, cursor);
475
476 if (isSectionHeaderDisplayEnabled() && partitionIndex == getIndexedPartition()) {
477 updateIndexer(cursor);
478 }
Tyler Gunn232df2f2014-04-15 15:30:58 -0700479
480 // When the cursor changes, cancel any pending asynchronous photo loads.
Brian Attwellb92b6372014-07-21 23:39:35 -0700481 mPhotoLoader.cancelPendingRequests(mFragmentRootView);
Chiao Cheng89437e82012-11-01 13:41:51 -0700482 }
483
484 public void changeCursor(Cursor cursor) {
485 changeCursor(0, cursor);
486 }
487
488 /**
489 * Updates the indexer, which is used to produce section headers.
490 */
491 private void updateIndexer(Cursor cursor) {
Walter Jangf1b15962016-03-25 12:26:06 -0700492 if (cursor == null || cursor.isClosed()) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700493 setIndexer(null);
494 return;
495 }
496
497 Bundle bundle = cursor.getExtras();
Yorke Lee6fa0d212014-07-29 18:11:59 -0700498 if (bundle.containsKey(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES) &&
499 bundle.containsKey(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS)) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700500 String sections[] =
Yorke Lee6fa0d212014-07-29 18:11:59 -0700501 bundle.getStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
502 int counts[] = bundle.getIntArray(
503 Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
Brian Attwell2101c3d2014-07-16 16:10:30 -0700504
505 if (getExtraStartingSection()) {
506 // Insert an additional unnamed section at the top of the list.
507 String allSections[] = new String[sections.length + 1];
508 int allCounts[] = new int[counts.length + 1];
509 for (int i = 0; i < sections.length; i++) {
510 allSections[i + 1] = sections[i];
511 allCounts[i + 1] = counts[i];
512 }
513 allCounts[0] = 1;
514 allSections[0] = "";
515 setIndexer(new ContactsSectionIndexer(allSections, allCounts));
516 } else {
517 setIndexer(new ContactsSectionIndexer(sections, counts));
518 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700519 } else {
520 setIndexer(null);
521 }
522 }
523
Brian Attwell2101c3d2014-07-16 16:10:30 -0700524 protected boolean getExtraStartingSection() {
525 return false;
526 }
527
Chiao Cheng89437e82012-11-01 13:41:51 -0700528 @Override
529 public int getViewTypeCount() {
530 // We need a separate view type for each item type, plus another one for
531 // each type with header, plus one for "other".
532 return getItemViewTypeCount() * 2 + 1;
533 }
534
535 @Override
536 public int getItemViewType(int partitionIndex, int position) {
537 int type = super.getItemViewType(partitionIndex, position);
Wenyi Wangbe88bed2016-05-13 12:04:14 -0700538 if (isSectionHeaderDisplayEnabled() && partitionIndex == getIndexedPartition()) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700539 Placement placement = getItemPlacementInSection(position);
540 return placement.firstInSection ? type : getItemViewTypeCount() + type;
541 } else {
542 return type;
543 }
544 }
545
546 @Override
547 public boolean isEmpty() {
548 // TODO
549// if (contactsListActivity.mProviderStatus != ProviderStatus.STATUS_NORMAL) {
550// return true;
551// }
552
553 if (!mEmptyListEnabled) {
554 return false;
555 } else if (isSearchMode()) {
556 return TextUtils.isEmpty(getQueryString());
Chiao Cheng89437e82012-11-01 13:41:51 -0700557 } else {
558 return super.isEmpty();
559 }
560 }
561
562 public boolean isLoading() {
563 int count = getPartitionCount();
564 for (int i = 0; i < count; i++) {
565 Partition partition = getPartition(i);
566 if (partition instanceof DirectoryPartition
567 && ((DirectoryPartition) partition).isLoading()) {
568 return true;
569 }
570 }
571 return false;
572 }
573
574 public boolean areAllPartitionsEmpty() {
575 int count = getPartitionCount();
576 for (int i = 0; i < count; i++) {
577 if (!isPartitionEmpty(i)) {
578 return false;
579 }
580 }
581 return true;
582 }
583
584 /**
585 * Changes visibility parameters for the default directory partition.
586 */
587 public void configureDefaultPartition(boolean showIfEmpty, boolean hasHeader) {
588 int defaultPartitionIndex = -1;
589 int count = getPartitionCount();
590 for (int i = 0; i < count; i++) {
591 Partition partition = getPartition(i);
592 if (partition instanceof DirectoryPartition &&
593 ((DirectoryPartition)partition).getDirectoryId() == Directory.DEFAULT) {
594 defaultPartitionIndex = i;
595 break;
596 }
597 }
598 if (defaultPartitionIndex != -1) {
599 setShowIfEmpty(defaultPartitionIndex, showIfEmpty);
600 setHasHeader(defaultPartitionIndex, hasHeader);
601 }
602 }
603
604 @Override
605 protected View newHeaderView(Context context, int partition, Cursor cursor,
606 ViewGroup parent) {
607 LayoutInflater inflater = LayoutInflater.from(context);
Brian Attwellaaa4e642014-08-11 14:38:26 -0700608 View view = inflater.inflate(R.layout.directory_header, parent, false);
609 if (!getPinnedPartitionHeadersEnabled()) {
610 // If the headers are unpinned, there is no need for their background
611 // color to be non-transparent. Setting this transparent reduces maintenance for
612 // non-pinned headers. We don't need to bother synchronizing the activity's
613 // background color with the header background color.
614 view.setBackground(null);
615 }
616 return view;
Chiao Cheng89437e82012-11-01 13:41:51 -0700617 }
618
Tony Mak6473f8f2016-02-23 20:39:16 +0000619 protected void bindWorkProfileIcon(final ContactListItemView view, int partitionId) {
620 final Partition partition = getPartition(partitionId);
621 if (partition instanceof DirectoryPartition) {
622 final DirectoryPartition directoryPartition = (DirectoryPartition) partition;
623 final long directoryId = directoryPartition.getDirectoryId();
624 final long userType = ContactsUtils.determineUserType(directoryId, null);
625 view.setWorkProfileIconEnabled(userType == ContactsUtils.USER_TYPE_WORK);
626 }
627 }
628
Chiao Cheng89437e82012-11-01 13:41:51 -0700629 @Override
630 protected void bindHeaderView(View view, int partitionIndex, Cursor cursor) {
631 Partition partition = getPartition(partitionIndex);
632 if (!(partition instanceof DirectoryPartition)) {
633 return;
634 }
635
636 DirectoryPartition directoryPartition = (DirectoryPartition)partition;
637 long directoryId = directoryPartition.getDirectoryId();
638 TextView labelTextView = (TextView)view.findViewById(R.id.label);
639 TextView displayNameTextView = (TextView)view.findViewById(R.id.display_name);
Alon Albertdd1dcc62013-08-28 14:02:47 -0700640 labelTextView.setText(directoryPartition.getLabel());
Ricky Waif600f072016-04-25 12:13:09 +0100641 if (!DirectoryCompat.isRemoteDirectoryId(directoryId)) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700642 displayNameTextView.setText(null);
643 } else {
Chiao Cheng89437e82012-11-01 13:41:51 -0700644 String directoryName = directoryPartition.getDisplayName();
645 String displayName = !TextUtils.isEmpty(directoryName)
646 ? directoryName
647 : directoryPartition.getDirectoryType();
648 displayNameTextView.setText(displayName);
649 }
Nancy Chene20d1ac2014-08-15 12:19:29 -0700650
651 final Resources res = getContext().getResources();
652 final int headerPaddingTop = partitionIndex == 1 && getPartition(0).isEmpty()?
653 0 : res.getDimensionPixelOffset(R.dimen.directory_header_extra_top_padding);
654 // There should be no extra padding at the top of the first directory header
655 view.setPaddingRelative(view.getPaddingStart(), headerPaddingTop, view.getPaddingEnd(),
656 view.getPaddingBottom());
Chiao Cheng89437e82012-11-01 13:41:51 -0700657 }
658
Jay Shrauner29884ed2013-08-27 15:57:52 -0700659 // Default implementation simply returns number of rows in the cursor.
660 // Broken out into its own routine so can be overridden by child classes
661 // for eg number of unique contacts for a phone list.
662 protected int getResultCount(Cursor cursor) {
663 return cursor == null ? 0 : cursor.getCount();
664 }
665
Chiao Cheng89437e82012-11-01 13:41:51 -0700666 // TODO: fix PluralRules to handle zero correctly and use Resources.getQuantityText directly
667 public String getQuantityText(int count, int zeroResourceId, int pluralResourceId) {
668 if (count == 0) {
669 return getContext().getString(zeroResourceId);
670 } else {
671 String format = getContext().getResources()
672 .getQuantityText(pluralResourceId, count).toString();
673 return String.format(format, count);
674 }
675 }
676
677 public boolean isPhotoSupported(int partitionIndex) {
678 Partition partition = getPartition(partitionIndex);
679 if (partition instanceof DirectoryPartition) {
680 return ((DirectoryPartition) partition).isPhotoSupported();
681 }
682 return true;
683 }
684
685 /**
686 * Returns the currently selected filter.
687 */
688 public ContactListFilter getFilter() {
689 return mFilter;
690 }
691
692 public void setFilter(ContactListFilter filter) {
693 mFilter = filter;
694 }
695
696 // TODO: move sharable logic (bindXX() methods) to here with extra arguments
697
698 /**
699 * Loads the photo for the quick contact view and assigns the contact uri.
700 * @param photoIdColumn Index of the photo id column
701 * @param photoUriColumn Index of the photo uri column. Optional: Can be -1
702 * @param contactIdColumn Index of the contact id column
703 * @param lookUpKeyColumn Index of the lookup key column
Yorke Lee9df5e192014-02-12 14:58:25 -0800704 * @param displayNameColumn Index of the display name column
Chiao Cheng89437e82012-11-01 13:41:51 -0700705 */
706 protected void bindQuickContact(final ContactListItemView view, int partitionIndex,
707 Cursor cursor, int photoIdColumn, int photoUriColumn, int contactIdColumn,
Yorke Lee9df5e192014-02-12 14:58:25 -0800708 int lookUpKeyColumn, int displayNameColumn) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700709 long photoId = 0;
710 if (!cursor.isNull(photoIdColumn)) {
711 photoId = cursor.getLong(photoIdColumn);
712 }
713
714 QuickContactBadge quickContact = view.getQuickContact();
715 quickContact.assignContactUri(
716 getContactUri(partitionIndex, cursor, contactIdColumn, lookUpKeyColumn));
Wenyi Wang6fa570f2015-11-24 15:44:11 -0800717 if (CompatUtils.hasPrioritizedMimeType()) {
718 // The Contacts app never uses the QuickContactBadge. Therefore, it is safe to assume
719 // that only Dialer will use this QuickContact badge. This means prioritizing the phone
720 // mimetype here is reasonable.
721 quickContact.setPrioritizedMimeType(Phone.CONTENT_ITEM_TYPE);
722 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700723
724 if (photoId != 0 || photoUriColumn == -1) {
Yorke Leec4a2a232014-04-28 17:53:42 -0700725 getPhotoLoader().loadThumbnail(quickContact, photoId, mDarkTheme, mCircularPhotos,
726 null);
Chiao Cheng89437e82012-11-01 13:41:51 -0700727 } else {
728 final String photoUriString = cursor.getString(photoUriColumn);
729 final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
Yorke Lee9df5e192014-02-12 14:58:25 -0800730 DefaultImageRequest request = null;
731 if (photoUri == null) {
732 request = getDefaultImageRequestFromCursor(cursor, displayNameColumn,
733 lookUpKeyColumn);
734 }
Yorke Leec4a2a232014-04-28 17:53:42 -0700735 getPhotoLoader().loadPhoto(quickContact, photoUri, -1, mDarkTheme, mCircularPhotos,
736 request);
Chiao Cheng89437e82012-11-01 13:41:51 -0700737 }
738
739 }
740
Brian Attwell2ea151c2014-09-03 19:53:26 -0700741 @Override
742 public boolean hasStableIds() {
743 // Whenever bindViewId() is called, the values passed into setId() are stable or
744 // stable-ish. For example, when one contact is modified we don't expect a second
745 // contact's Contact._ID values to change.
746 return true;
747 }
748
749 protected void bindViewId(final ContactListItemView view, Cursor cursor, int idColumn) {
750 // Set a semi-stable id, so that talkback won't get confused when the list gets
751 // refreshed. There is little harm in inserting the same ID twice.
752 long contactId = cursor.getLong(idColumn);
753 view.setId((int) (contactId % Integer.MAX_VALUE));
754
755 }
756
Chiao Cheng89437e82012-11-01 13:41:51 -0700757 protected Uri getContactUri(int partitionIndex, Cursor cursor,
758 int contactIdColumn, int lookUpKeyColumn) {
759 long contactId = cursor.getLong(contactIdColumn);
760 String lookupKey = cursor.getString(lookUpKeyColumn);
Chiao Cheng89437e82012-11-01 13:41:51 -0700761 long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
Jay Shrauner745da9e2013-11-06 13:25:23 -0800762 Uri uri = Contacts.getLookupUri(contactId, lookupKey);
Jay Shrauner41b61c82015-03-13 11:28:47 -0700763 if (uri != null && directoryId != Directory.DEFAULT) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700764 uri = uri.buildUpon().appendQueryParameter(
765 ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
766 }
767 return uri;
768 }
769
Yorke Lee9df5e192014-02-12 14:58:25 -0800770 /**
771 * Retrieves the lookup key and display name from a cursor, and returns a
772 * {@link DefaultImageRequest} containing these contact details
773 *
774 * @param cursor Contacts cursor positioned at the current row to retrieve contact details for
775 * @param displayNameColumn Column index of the display name
776 * @param lookupKeyColumn Column index of the lookup key
777 * @return {@link DefaultImageRequest} with the displayName and identifier fields set to the
778 * display name and lookup key of the contact.
779 */
Yorke Leec4a2a232014-04-28 17:53:42 -0700780 public DefaultImageRequest getDefaultImageRequestFromCursor(Cursor cursor,
Yorke Lee9df5e192014-02-12 14:58:25 -0800781 int displayNameColumn, int lookupKeyColumn) {
782 final String displayName = cursor.getString(displayNameColumn);
783 final String lookupKey = cursor.getString(lookupKeyColumn);
Yorke Leec4a2a232014-04-28 17:53:42 -0700784 return new DefaultImageRequest(displayName, lookupKey, mCircularPhotos);
Yorke Lee9df5e192014-02-12 14:58:25 -0800785 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700786}