Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 1 | /* |
| 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 Mai | 69c182a | 2016-12-05 13:07:03 -0800 | [diff] [blame] | 16 | package com.android.contacts.list; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 17 | |
| 18 | import android.content.Context; |
| 19 | import android.content.CursorLoader; |
Nancy Chen | e20d1ac | 2014-08-15 12:19:29 -0700 | [diff] [blame] | 20 | import android.content.res.Resources; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 21 | import android.database.Cursor; |
| 22 | import android.net.Uri; |
| 23 | import android.os.Bundle; |
| 24 | import android.provider.ContactsContract; |
Brian Attwell | bfd3c47 | 2015-02-25 22:33:02 -0800 | [diff] [blame] | 25 | import android.provider.ContactsContract.CommonDataKinds.Phone; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 26 | import android.provider.ContactsContract.Contacts; |
| 27 | import android.provider.ContactsContract.Directory; |
| 28 | import android.text.TextUtils; |
| 29 | import android.util.Log; |
| 30 | import android.view.LayoutInflater; |
| 31 | import android.view.View; |
| 32 | import android.view.ViewGroup; |
| 33 | import android.widget.QuickContactBadge; |
| 34 | import android.widget.SectionIndexer; |
| 35 | import android.widget.TextView; |
| 36 | |
Gary Mai | 0a49afa | 2016-12-05 15:53:58 -0800 | [diff] [blame] | 37 | import com.android.contacts.ContactPhotoManager; |
| 38 | import com.android.contacts.ContactPhotoManager.DefaultImageRequest; |
| 39 | import com.android.contacts.ContactsUtils; |
Arthur Wang | 3f6a244 | 2016-12-05 14:51:59 -0800 | [diff] [blame] | 40 | import com.android.contacts.R; |
Gary Mai | 69c182a | 2016-12-05 13:07:03 -0800 | [diff] [blame] | 41 | import com.android.contacts.compat.CompatUtils; |
| 42 | import com.android.contacts.compat.DirectoryCompat; |
| 43 | import com.android.contacts.util.SearchUtil; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 44 | |
| 45 | import java.util.HashSet; |
| 46 | |
| 47 | /** |
| 48 | * Common base class for various contact-related lists, e.g. contact list, phone number list |
| 49 | * etc. |
| 50 | */ |
| 51 | public 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 Lee | c4a2a23 | 2014-04-28 17:53:42 -0700 | [diff] [blame] | 65 | private boolean mCircularPhotos = true; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 66 | private boolean mQuickContactEnabled; |
Andrew Lee | 4683e54 | 2014-06-09 16:24:10 -0700 | [diff] [blame] | 67 | private boolean mAdjustSelectionBoundsEnabled; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 68 | |
| 69 | /** |
Wenyi Wang | 25774d2 | 2016-04-08 11:15:11 -0700 | [diff] [blame] | 70 | * indicates if contact queries include favorites |
| 71 | */ |
| 72 | private boolean mIncludeFavorites; |
| 73 | |
Wenyi Wang | c9ad9b1 | 2016-05-20 15:10:12 -0700 | [diff] [blame] | 74 | private int mNumberOfFavorites; |
| 75 | |
Wenyi Wang | 25774d2 | 2016-04-08 11:15:11 -0700 | [diff] [blame] | 76 | /** |
Brian Attwell | b92b637 | 2014-07-21 23:39:35 -0700 | [diff] [blame] | 77 | * The root view of the fragment that this adapter is associated with. |
| 78 | */ |
| 79 | private View mFragmentRootView; |
| 80 | |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 81 | private ContactPhotoManager mPhotoLoader; |
| 82 | |
| 83 | private String mQueryString; |
Chiao Cheng | a1554ef | 2012-12-21 15:45:54 -0800 | [diff] [blame] | 84 | private String mUpperCaseQueryString; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 85 | private boolean mSearchMode; |
| 86 | private int mDirectorySearchMode; |
| 87 | private int mDirectoryResultLimit = Integer.MAX_VALUE; |
| 88 | |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 89 | private boolean mEmptyListEnabled = true; |
| 90 | |
| 91 | private boolean mSelectionVisible; |
| 92 | |
| 93 | private ContactListFilter mFilter; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 94 | 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 101 | setDefaultFilterHeaderText(R.string.local_search_label); |
Yorke Lee | d9b5420 | 2013-09-11 12:03:41 -0700 | [diff] [blame] | 102 | addPartitions(); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 103 | } |
| 104 | |
Brian Attwell | b92b637 | 2014-07-21 23:39:35 -0700 | [diff] [blame] | 105 | /** |
| 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 113 | protected void setDefaultFilterHeaderText(int resourceId) { |
| 114 | mDefaultFilterHeaderText = getContext().getResources().getText(resourceId); |
| 115 | } |
| 116 | |
| 117 | @Override |
Andrew Lee | 020ba62 | 2014-04-25 15:26:35 -0700 | [diff] [blame] | 118 | 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 Lee | 4683e54 | 2014-06-09 16:24:10 -0700 | [diff] [blame] | 122 | view.setAdjustSelectionBoundsEnabled(isAdjustSelectionBoundsEnabled()); |
Andrew Lee | 020ba62 | 2014-04-25 15:26:35 -0700 | [diff] [blame] | 123 | return view; |
| 124 | } |
| 125 | |
| 126 | @Override |
Brian Attwell | a5ad557 | 2014-09-15 11:18:03 -0700 | [diff] [blame] | 127 | protected void bindView(View itemView, int partition, Cursor cursor, int position) { |
| 128 | final ContactListItemView view = (ContactListItemView) itemView; |
| 129 | view.setIsSectionHeaderEnabled(isSectionHeaderDisplayEnabled()); |
Tony Mak | 6473f8f | 2016-02-23 20:39:16 +0000 | [diff] [blame] | 130 | bindWorkProfileIcon(view, partition); |
Brian Attwell | a5ad557 | 2014-09-15 11:18:03 -0700 | [diff] [blame] | 131 | } |
| 132 | |
| 133 | @Override |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 134 | protected View createPinnedSectionHeaderView(Context context, ViewGroup parent) { |
Yorke Lee | 525f513 | 2014-07-30 18:59:28 -0700 | [diff] [blame] | 135 | return new ContactListPinnedHeaderView(context, null, parent); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 136 | } |
| 137 | |
| 138 | @Override |
| 139 | protected void setPinnedSectionTitle(View pinnedHeaderView, String title) { |
Andrew Lee | 020ba62 | 2014-04-25 15:26:35 -0700 | [diff] [blame] | 140 | ((ContactListPinnedHeaderView) pinnedHeaderView).setSectionHeaderTitle(title); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 141 | } |
| 142 | |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 143 | 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 Lee | d9b5420 | 2013-09-11 12:03:41 -0700 | [diff] [blame] | 153 | partition.setLabel(mDefaultFilterHeaderText.toString()); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 154 | 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 Gilad | 03a533a | 2013-08-21 09:46:00 -0700 | [diff] [blame] | 179 | protected int getPartitionByDirectoryId(long id) { |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 180 | 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 Gilad | 03a533a | 2013-08-21 09:46:00 -0700 | [diff] [blame] | 192 | 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 206 | 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 Cheng | ecba27e | 2012-12-27 17:14:53 -0800 | [diff] [blame] | 260 | mUpperCaseQueryString = SearchUtil |
| 261 | .cleanStartAndEndOfSearchQuery(queryString.toUpperCase()) ; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 262 | } |
| 263 | } |
| 264 | |
Chiao Cheng | a1554ef | 2012-12-21 15:45:54 -0800 | [diff] [blame] | 265 | public String getUpperCaseQueryString() { |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 266 | 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 Gilad | 03a533a | 2013-08-21 09:46:00 -0700 | [diff] [blame] | 281 | public int getDirectoryResultLimit(DirectoryPartition directoryPartition) { |
| 282 | final int limit = directoryPartition.getResultLimit(); |
| 283 | return limit == DirectoryPartition.RESULT_LIMIT_DEFAULT ? mDirectoryResultLimit : limit; |
| 284 | } |
| 285 | |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 286 | 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 Lee | c4a2a23 | 2014-04-28 17:53:42 -0700 | [diff] [blame] | 322 | public boolean getCircularPhotos() { |
| 323 | return mCircularPhotos; |
| 324 | } |
| 325 | |
| 326 | public void setCircularPhotos(boolean circularPhotos) { |
| 327 | mCircularPhotos = circularPhotos; |
| 328 | } |
| 329 | |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 330 | 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 Lee | 4683e54 | 2014-06-09 16:24:10 -0700 | [diff] [blame] | 354 | public boolean isAdjustSelectionBoundsEnabled() { |
| 355 | return mAdjustSelectionBoundsEnabled; |
| 356 | } |
| 357 | |
| 358 | public void setAdjustSelectionBoundsEnabled(boolean enabled) { |
| 359 | mAdjustSelectionBoundsEnabled = enabled; |
| 360 | } |
| 361 | |
Wenyi Wang | 25774d2 | 2016-04-08 11:15:11 -0700 | [diff] [blame] | 362 | public boolean shouldIncludeFavorites() { |
| 363 | return mIncludeFavorites; |
| 364 | } |
| 365 | |
| 366 | public void setIncludeFavorites(boolean includeFavorites) { |
| 367 | mIncludeFavorites = includeFavorites; |
| 368 | } |
| 369 | |
Wenyi Wang | 25774d2 | 2016-04-08 11:15:11 -0700 | [diff] [blame] | 370 | public void setFavoritesSectionHeader(int numberOfFavorites) { |
| 371 | if (mIncludeFavorites) { |
Wenyi Wang | c9ad9b1 | 2016-05-20 15:10:12 -0700 | [diff] [blame] | 372 | mNumberOfFavorites = numberOfFavorites; |
John Shao | da18871 | 2016-08-19 14:57:11 -0700 | [diff] [blame] | 373 | setSectionHeader(numberOfFavorites); |
Wenyi Wang | 25774d2 | 2016-04-08 11:15:11 -0700 | [diff] [blame] | 374 | } |
| 375 | } |
| 376 | |
Wenyi Wang | c9ad9b1 | 2016-05-20 15:10:12 -0700 | [diff] [blame] | 377 | public int getNumberOfFavorites() { |
Tingting Wang | 54205a8 | 2016-07-20 14:27:18 -0700 | [diff] [blame] | 378 | return mNumberOfFavorites; |
Wenyi Wang | c9ad9b1 | 2016-05-20 15:10:12 -0700 | [diff] [blame] | 379 | } |
| 380 | |
John Shao | da18871 | 2016-08-19 14:57:11 -0700 | [diff] [blame] | 381 | private void setSectionHeader(int numberOfItems) { |
Wenyi Wang | 25774d2 | 2016-04-08 11:15:11 -0700 | [diff] [blame] | 382 | SectionIndexer indexer = getIndexer(); |
| 383 | if (indexer != null) { |
John Shao | da18871 | 2016-08-19 14:57:11 -0700 | [diff] [blame] | 384 | ((ContactsSectionIndexer) indexer).setFavoritesHeader(numberOfItems); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 385 | } |
| 386 | } |
| 387 | |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 388 | 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 Wai | f600f07 | 2016-04-25 12:13:09 +0100 | [diff] [blame] | 420 | if (DirectoryCompat.isRemoteDirectoryId(id)) { |
Victor Chang | 3e21eef | 2015-12-24 13:17:42 +0800 | [diff] [blame] | 421 | 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 Albert | dd1dcc6 | 2013-08-28 14:02:47 -0700 | [diff] [blame] | 426 | } else { |
Victor Chang | 3e21eef | 2015-12-24 13:17:42 +0800 | [diff] [blame] | 427 | if (DirectoryCompat.isEnterpriseDirectoryId(id)) { |
| 428 | partition.setLabel(mContext.getString(R.string.list_filter_phones_work)); |
| 429 | } else { |
| 430 | partition.setLabel(mDefaultFilterHeaderText.toString()); |
| 431 | } |
Alon Albert | dd1dcc6 | 2013-08-28 14:02:47 -0700 | [diff] [blame] | 432 | } |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 433 | 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 Gunn | 232df2f | 2014-04-15 15:30:58 -0700 | [diff] [blame] | 479 | |
| 480 | // When the cursor changes, cancel any pending asynchronous photo loads. |
Brian Attwell | b92b637 | 2014-07-21 23:39:35 -0700 | [diff] [blame] | 481 | mPhotoLoader.cancelPendingRequests(mFragmentRootView); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 482 | } |
| 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 Jang | f1b1596 | 2016-03-25 12:26:06 -0700 | [diff] [blame] | 492 | if (cursor == null || cursor.isClosed()) { |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 493 | setIndexer(null); |
| 494 | return; |
| 495 | } |
| 496 | |
| 497 | Bundle bundle = cursor.getExtras(); |
Yorke Lee | 6fa0d21 | 2014-07-29 18:11:59 -0700 | [diff] [blame] | 498 | if (bundle.containsKey(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES) && |
| 499 | bundle.containsKey(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS)) { |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 500 | String sections[] = |
Yorke Lee | 6fa0d21 | 2014-07-29 18:11:59 -0700 | [diff] [blame] | 501 | bundle.getStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES); |
| 502 | int counts[] = bundle.getIntArray( |
| 503 | Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS); |
Brian Attwell | 2101c3d | 2014-07-16 16:10:30 -0700 | [diff] [blame] | 504 | |
| 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 519 | } else { |
| 520 | setIndexer(null); |
| 521 | } |
| 522 | } |
| 523 | |
Brian Attwell | 2101c3d | 2014-07-16 16:10:30 -0700 | [diff] [blame] | 524 | protected boolean getExtraStartingSection() { |
| 525 | return false; |
| 526 | } |
| 527 | |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 528 | @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 Wang | be88bed | 2016-05-13 12:04:14 -0700 | [diff] [blame] | 538 | if (isSectionHeaderDisplayEnabled() && partitionIndex == getIndexedPartition()) { |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 539 | 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 557 | } 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 Attwell | aaa4e64 | 2014-08-11 14:38:26 -0700 | [diff] [blame] | 608 | 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 617 | } |
| 618 | |
Tony Mak | 6473f8f | 2016-02-23 20:39:16 +0000 | [diff] [blame] | 619 | 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 629 | @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 Albert | dd1dcc6 | 2013-08-28 14:02:47 -0700 | [diff] [blame] | 640 | labelTextView.setText(directoryPartition.getLabel()); |
Ricky Wai | f600f07 | 2016-04-25 12:13:09 +0100 | [diff] [blame] | 641 | if (!DirectoryCompat.isRemoteDirectoryId(directoryId)) { |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 642 | displayNameTextView.setText(null); |
| 643 | } else { |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 644 | String directoryName = directoryPartition.getDisplayName(); |
| 645 | String displayName = !TextUtils.isEmpty(directoryName) |
| 646 | ? directoryName |
| 647 | : directoryPartition.getDirectoryType(); |
| 648 | displayNameTextView.setText(displayName); |
| 649 | } |
Nancy Chen | e20d1ac | 2014-08-15 12:19:29 -0700 | [diff] [blame] | 650 | |
| 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 657 | } |
| 658 | |
Jay Shrauner | 29884ed | 2013-08-27 15:57:52 -0700 | [diff] [blame] | 659 | // 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 666 | // 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 Lee | 9df5e19 | 2014-02-12 14:58:25 -0800 | [diff] [blame] | 704 | * @param displayNameColumn Index of the display name column |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 705 | */ |
| 706 | protected void bindQuickContact(final ContactListItemView view, int partitionIndex, |
| 707 | Cursor cursor, int photoIdColumn, int photoUriColumn, int contactIdColumn, |
Yorke Lee | 9df5e19 | 2014-02-12 14:58:25 -0800 | [diff] [blame] | 708 | int lookUpKeyColumn, int displayNameColumn) { |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 709 | 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 Wang | 6fa570f | 2015-11-24 15:44:11 -0800 | [diff] [blame] | 717 | 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 723 | |
| 724 | if (photoId != 0 || photoUriColumn == -1) { |
Yorke Lee | c4a2a23 | 2014-04-28 17:53:42 -0700 | [diff] [blame] | 725 | getPhotoLoader().loadThumbnail(quickContact, photoId, mDarkTheme, mCircularPhotos, |
| 726 | null); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 727 | } else { |
| 728 | final String photoUriString = cursor.getString(photoUriColumn); |
| 729 | final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString); |
Yorke Lee | 9df5e19 | 2014-02-12 14:58:25 -0800 | [diff] [blame] | 730 | DefaultImageRequest request = null; |
| 731 | if (photoUri == null) { |
| 732 | request = getDefaultImageRequestFromCursor(cursor, displayNameColumn, |
| 733 | lookUpKeyColumn); |
| 734 | } |
Yorke Lee | c4a2a23 | 2014-04-28 17:53:42 -0700 | [diff] [blame] | 735 | getPhotoLoader().loadPhoto(quickContact, photoUri, -1, mDarkTheme, mCircularPhotos, |
| 736 | request); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 737 | } |
| 738 | |
| 739 | } |
| 740 | |
Brian Attwell | 2ea151c | 2014-09-03 19:53:26 -0700 | [diff] [blame] | 741 | @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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 757 | 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 761 | long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId(); |
Jay Shrauner | 745da9e | 2013-11-06 13:25:23 -0800 | [diff] [blame] | 762 | Uri uri = Contacts.getLookupUri(contactId, lookupKey); |
Jay Shrauner | 41b61c8 | 2015-03-13 11:28:47 -0700 | [diff] [blame] | 763 | if (uri != null && directoryId != Directory.DEFAULT) { |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 764 | uri = uri.buildUpon().appendQueryParameter( |
| 765 | ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build(); |
| 766 | } |
| 767 | return uri; |
| 768 | } |
| 769 | |
Yorke Lee | 9df5e19 | 2014-02-12 14:58:25 -0800 | [diff] [blame] | 770 | /** |
| 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 Lee | c4a2a23 | 2014-04-28 17:53:42 -0700 | [diff] [blame] | 780 | public DefaultImageRequest getDefaultImageRequestFromCursor(Cursor cursor, |
Yorke Lee | 9df5e19 | 2014-02-12 14:58:25 -0800 | [diff] [blame] | 781 | int displayNameColumn, int lookupKeyColumn) { |
| 782 | final String displayName = cursor.getString(displayNameColumn); |
| 783 | final String lookupKey = cursor.getString(lookupKeyColumn); |
Yorke Lee | c4a2a23 | 2014-04-28 17:53:42 -0700 | [diff] [blame] | 784 | return new DefaultImageRequest(displayName, lookupKey, mCircularPhotos); |
Yorke Lee | 9df5e19 | 2014-02-12 14:58:25 -0800 | [diff] [blame] | 785 | } |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 786 | } |