blob: cb63f53eb77d469e54f40215bed3b3c738e88631 [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.database.Cursor;
20import android.net.Uri;
21import android.provider.ContactsContract;
Chiao Cheng89437e82012-11-01 13:41:51 -070022import android.provider.ContactsContract.Contacts;
Tingting Wang0ac73ba2016-07-05 22:33:01 -070023import android.provider.ContactsContract.Data;
Chiao Cheng89437e82012-11-01 13:41:51 -070024import android.provider.ContactsContract.Directory;
Yorke Leea12a04c2014-07-01 10:39:42 -070025import android.provider.ContactsContract.SearchSnippets;
Chiao Cheng89437e82012-11-01 13:41:51 -070026import android.text.TextUtils;
Chiao Cheng89437e82012-11-01 13:41:51 -070027import android.view.ViewGroup;
28import android.widget.ListView;
29
Gary Mai0a49afa2016-12-05 15:53:58 -080030import com.android.contacts.ContactPhotoManager.DefaultImageRequest;
Arthur Wang3f6a2442016-12-05 14:51:59 -080031import com.android.contacts.R;
Gary Mai69c182a2016-12-05 13:07:03 -080032import com.android.contacts.compat.ContactsCompat;
33import com.android.contacts.preference.ContactsPreferences;
Chiao Cheng89437e82012-11-01 13:41:51 -070034
Wenyi Wang25774d22016-04-08 11:15:11 -070035import java.util.HashSet;
36import java.util.Set;
37
Chiao Cheng89437e82012-11-01 13:41:51 -070038/**
39 * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
Chiao Cheng89437e82012-11-01 13:41:51 -070040 */
Walter Jang6375f532016-05-11 18:17:50 -070041public abstract class ContactListAdapter extends MultiSelectEntryContactListAdapter {
Chiao Cheng89437e82012-11-01 13:41:51 -070042
Walter Jang08379b12016-06-14 15:35:28 -070043 public static class ContactQuery {
Marcus Hagerott819214d2016-09-29 14:58:27 -070044 public static final String[] CONTACT_PROJECTION_PRIMARY = new String[] {
Chiao Cheng89437e82012-11-01 13:41:51 -070045 Contacts._ID, // 0
46 Contacts.DISPLAY_NAME_PRIMARY, // 1
47 Contacts.CONTACT_PRESENCE, // 2
48 Contacts.CONTACT_STATUS, // 3
49 Contacts.PHOTO_ID, // 4
50 Contacts.PHOTO_THUMBNAIL_URI, // 5
51 Contacts.LOOKUP_KEY, // 6
Wenyi Wangbe88bed2016-05-13 12:04:14 -070052 Contacts.PHONETIC_NAME, // 7
Wenyi Wang25774d22016-04-08 11:15:11 -070053 Contacts.STARRED, // 9
Chiao Cheng89437e82012-11-01 13:41:51 -070054 };
55
56 private static final String[] CONTACT_PROJECTION_ALTERNATIVE = new String[] {
57 Contacts._ID, // 0
58 Contacts.DISPLAY_NAME_ALTERNATIVE, // 1
59 Contacts.CONTACT_PRESENCE, // 2
60 Contacts.CONTACT_STATUS, // 3
61 Contacts.PHOTO_ID, // 4
62 Contacts.PHOTO_THUMBNAIL_URI, // 5
63 Contacts.LOOKUP_KEY, // 6
Wenyi Wangbe88bed2016-05-13 12:04:14 -070064 Contacts.PHONETIC_NAME, // 7
65 Contacts.STARRED, // 8
Chiao Cheng89437e82012-11-01 13:41:51 -070066 };
67
68 private static final String[] FILTER_PROJECTION_PRIMARY = new String[] {
69 Contacts._ID, // 0
70 Contacts.DISPLAY_NAME_PRIMARY, // 1
71 Contacts.CONTACT_PRESENCE, // 2
72 Contacts.CONTACT_STATUS, // 3
73 Contacts.PHOTO_ID, // 4
74 Contacts.PHOTO_THUMBNAIL_URI, // 5
75 Contacts.LOOKUP_KEY, // 6
Wenyi Wangbe88bed2016-05-13 12:04:14 -070076 Contacts.PHONETIC_NAME, // 7
77 Contacts.STARRED, // 8
78 SearchSnippets.SNIPPET, // 9
Chiao Cheng89437e82012-11-01 13:41:51 -070079 };
80
81 private static final String[] FILTER_PROJECTION_ALTERNATIVE = new String[] {
82 Contacts._ID, // 0
83 Contacts.DISPLAY_NAME_ALTERNATIVE, // 1
84 Contacts.CONTACT_PRESENCE, // 2
85 Contacts.CONTACT_STATUS, // 3
86 Contacts.PHOTO_ID, // 4
87 Contacts.PHOTO_THUMBNAIL_URI, // 5
88 Contacts.LOOKUP_KEY, // 6
Wenyi Wangbe88bed2016-05-13 12:04:14 -070089 Contacts.PHONETIC_NAME, // 7
90 Contacts.STARRED, // 8
91 SearchSnippets.SNIPPET, // 9
Chiao Cheng89437e82012-11-01 13:41:51 -070092 };
93
94 public static final int CONTACT_ID = 0;
95 public static final int CONTACT_DISPLAY_NAME = 1;
96 public static final int CONTACT_PRESENCE_STATUS = 2;
97 public static final int CONTACT_CONTACT_STATUS = 3;
98 public static final int CONTACT_PHOTO_ID = 4;
99 public static final int CONTACT_PHOTO_URI = 5;
100 public static final int CONTACT_LOOKUP_KEY = 6;
Wenyi Wangbe88bed2016-05-13 12:04:14 -0700101 public static final int CONTACT_PHONETIC_NAME = 7;
102 public static final int CONTACT_STARRED = 8;
103 public static final int CONTACT_SNIPPET = 9;
Walter Jang54564402016-01-18 11:56:19 -0800104 }
105
Chiao Cheng89437e82012-11-01 13:41:51 -0700106 private CharSequence mUnknownNameText;
107
108 private long mSelectedContactDirectoryId;
109 private String mSelectedContactLookupKey;
110 private long mSelectedContactId;
Paul Sliwowskife07fcd2013-09-10 22:08:40 -0700111 private ContactListItemView.PhotoPosition mPhotoPosition;
Chiao Cheng89437e82012-11-01 13:41:51 -0700112
113 public ContactListAdapter(Context context) {
Walter Jang6375f532016-05-11 18:17:50 -0700114 super(context, ContactQuery.CONTACT_ID);
Chiao Cheng89437e82012-11-01 13:41:51 -0700115
116 mUnknownNameText = context.getText(R.string.missing_name);
117 }
118
Paul Sliwowskife07fcd2013-09-10 22:08:40 -0700119 public void setPhotoPosition(ContactListItemView.PhotoPosition photoPosition) {
120 mPhotoPosition = photoPosition;
121 }
122
123 public ContactListItemView.PhotoPosition getPhotoPosition() {
124 return mPhotoPosition;
125 }
126
Chiao Cheng89437e82012-11-01 13:41:51 -0700127 public CharSequence getUnknownNameText() {
128 return mUnknownNameText;
129 }
130
131 public long getSelectedContactDirectoryId() {
132 return mSelectedContactDirectoryId;
133 }
134
135 public String getSelectedContactLookupKey() {
136 return mSelectedContactLookupKey;
137 }
138
139 public long getSelectedContactId() {
140 return mSelectedContactId;
141 }
142
143 public void setSelectedContact(long selectedDirectoryId, String lookupKey, long contactId) {
144 mSelectedContactDirectoryId = selectedDirectoryId;
145 mSelectedContactLookupKey = lookupKey;
146 mSelectedContactId = contactId;
147 }
148
149 protected static Uri buildSectionIndexerUri(Uri uri) {
150 return uri.buildUpon()
Yorke Lee9a598eb2014-09-04 14:59:22 -0700151 .appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true").build();
Chiao Cheng89437e82012-11-01 13:41:51 -0700152 }
153
154 @Override
155 public String getContactDisplayName(int position) {
156 return ((Cursor) getItem(position)).getString(ContactQuery.CONTACT_DISPLAY_NAME);
157 }
158
159 /**
160 * Builds the {@link Contacts#CONTENT_LOOKUP_URI} for the given
161 * {@link ListView} position.
162 */
163 public Uri getContactUri(int position) {
164 int partitionIndex = getPartitionForPosition(position);
165 Cursor item = (Cursor)getItem(position);
166 return item != null ? getContactUri(partitionIndex, item) : null;
167 }
168
169 public Uri getContactUri(int partitionIndex, Cursor cursor) {
170 long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
171 String lookupKey = cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY);
172 Uri uri = Contacts.getLookupUri(contactId, lookupKey);
173 long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
Jay Shrauner41b61c82015-03-13 11:28:47 -0700174 if (uri != null && directoryId != Directory.DEFAULT) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700175 uri = uri.buildUpon().appendQueryParameter(
176 ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
177 }
178 return uri;
179 }
180
Walter Jang08379b12016-06-14 15:35:28 -0700181 /**
182 * Returns the {@link Contacts#_ID} for the given {@link ListView} position.
183 */
184 public long getContactId(int position) {
185 final Cursor cursor = (Cursor) getItem(position);
186 return cursor == null ? -1 : cursor.getLong(ContactQuery.CONTACT_ID);
187 }
188
Ricky Waidffb27d2015-12-08 16:58:27 +0000189 public boolean isEnterpriseContact(int position) {
190 final Cursor cursor = (Cursor) getItem(position);
191 if (cursor != null) {
192 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
Wenyi Wangfe4048c2015-12-21 15:53:43 -0800193 return ContactsCompat.isEnterpriseContactId(contactId);
Ricky Waidffb27d2015-12-08 16:58:27 +0000194 }
195 return false;
196 }
197
Chiao Cheng89437e82012-11-01 13:41:51 -0700198 /**
199 * Returns true if the specified contact is selected in the list. For a
200 * contact to be shown as selected, we need both the directory and and the
201 * lookup key to be the same. We are paying no attention to the contactId,
202 * because it is volatile, especially in the case of directories.
203 */
204 public boolean isSelectedContact(int partitionIndex, Cursor cursor) {
205 long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
206 if (getSelectedContactDirectoryId() != directoryId) {
207 return false;
208 }
209 String lookupKey = getSelectedContactLookupKey();
210 if (lookupKey != null && TextUtils.equals(lookupKey,
211 cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY))) {
212 return true;
213 }
214
215 return directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE
216 && getSelectedContactId() == cursor.getLong(ContactQuery.CONTACT_ID);
217 }
218
219 @Override
Andrew Lee020ba622014-04-25 15:26:35 -0700220 protected ContactListItemView newView(
221 Context context, int partition, Cursor cursor, int position, ViewGroup parent) {
222 ContactListItemView view = super.newView(context, partition, cursor, position, parent);
Chiao Cheng89437e82012-11-01 13:41:51 -0700223 view.setUnknownNameText(mUnknownNameText);
224 view.setQuickContactEnabled(isQuickContactEnabled());
Andrew Lee4683e542014-06-09 16:24:10 -0700225 view.setAdjustSelectionBoundsEnabled(isAdjustSelectionBoundsEnabled());
Chiao Cheng89437e82012-11-01 13:41:51 -0700226 view.setActivatedStateSupported(isSelectionVisible());
Paul Sliwowskife07fcd2013-09-10 22:08:40 -0700227 if (mPhotoPosition != null) {
228 view.setPhotoPosition(mPhotoPosition);
229 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700230 return view;
231 }
232
233 protected void bindSectionHeaderAndDivider(ContactListItemView view, int position,
234 Cursor cursor) {
Yorke Lee9e100252014-05-01 15:49:05 -0700235 view.setIsSectionHeaderEnabled(isSectionHeaderDisplayEnabled());
Chiao Cheng89437e82012-11-01 13:41:51 -0700236 if (isSectionHeaderDisplayEnabled()) {
237 Placement placement = getItemPlacementInSection(position);
Chiao Cheng89437e82012-11-01 13:41:51 -0700238 view.setSectionHeader(placement.sectionHeader);
Chiao Cheng89437e82012-11-01 13:41:51 -0700239 } else {
240 view.setSectionHeader(null);
Chiao Cheng89437e82012-11-01 13:41:51 -0700241 }
242 }
243
244 protected void bindPhoto(final ContactListItemView view, int partitionIndex, Cursor cursor) {
245 if (!isPhotoSupported(partitionIndex)) {
246 view.removePhotoView();
247 return;
248 }
249
250 // Set the photo, if available
251 long photoId = 0;
252 if (!cursor.isNull(ContactQuery.CONTACT_PHOTO_ID)) {
253 photoId = cursor.getLong(ContactQuery.CONTACT_PHOTO_ID);
254 }
255
256 if (photoId != 0) {
Yorke Leec4a2a232014-04-28 17:53:42 -0700257 getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false,
258 getCircularPhotos(), null);
Chiao Cheng89437e82012-11-01 13:41:51 -0700259 } else {
260 final String photoUriString = cursor.getString(ContactQuery.CONTACT_PHOTO_URI);
261 final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
Yorke Lee9df5e192014-02-12 14:58:25 -0800262 DefaultImageRequest request = null;
263 if (photoUri == null) {
Yorke Leec4a2a232014-04-28 17:53:42 -0700264 request = getDefaultImageRequestFromCursor(cursor,
265 ContactQuery.CONTACT_DISPLAY_NAME,
266 ContactQuery.CONTACT_LOOKUP_KEY);
Yorke Lee9df5e192014-02-12 14:58:25 -0800267 }
Yorke Leec4a2a232014-04-28 17:53:42 -0700268 getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false,
269 getCircularPhotos(), request);
Chiao Cheng89437e82012-11-01 13:41:51 -0700270 }
271 }
272
Brian Attwell2ea151c2014-09-03 19:53:26 -0700273 protected void bindNameAndViewId(final ContactListItemView view, Cursor cursor) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700274 view.showDisplayName(
275 cursor, ContactQuery.CONTACT_DISPLAY_NAME, getContactNameDisplayOrder());
276 // Note: we don't show phonetic any more (See issue 5265330)
Brian Attwell2ea151c2014-09-03 19:53:26 -0700277
278 bindViewId(view, cursor, ContactQuery.CONTACT_ID);
Chiao Cheng89437e82012-11-01 13:41:51 -0700279 }
280
281 protected void bindPresenceAndStatusMessage(final ContactListItemView view, Cursor cursor) {
282 view.showPresenceAndStatusMessage(cursor, ContactQuery.CONTACT_PRESENCE_STATUS,
283 ContactQuery.CONTACT_CONTACT_STATUS);
284 }
285
286 protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) {
287 view.showSnippet(cursor, ContactQuery.CONTACT_SNIPPET);
288 }
289
290 public int getSelectedContactPosition() {
291 if (mSelectedContactLookupKey == null && mSelectedContactId == 0) {
292 return -1;
293 }
294
295 Cursor cursor = null;
296 int partitionIndex = -1;
297 int partitionCount = getPartitionCount();
298 for (int i = 0; i < partitionCount; i++) {
299 DirectoryPartition partition = (DirectoryPartition) getPartition(i);
300 if (partition.getDirectoryId() == mSelectedContactDirectoryId) {
301 partitionIndex = i;
302 break;
303 }
304 }
305 if (partitionIndex == -1) {
306 return -1;
307 }
308
309 cursor = getCursor(partitionIndex);
310 if (cursor == null) {
311 return -1;
312 }
313
314 cursor.moveToPosition(-1); // Reset cursor
315 int offset = -1;
316 while (cursor.moveToNext()) {
317 if (mSelectedContactLookupKey != null) {
318 String lookupKey = cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY);
319 if (mSelectedContactLookupKey.equals(lookupKey)) {
320 offset = cursor.getPosition();
321 break;
322 }
323 }
324 if (mSelectedContactId != 0 && (mSelectedContactDirectoryId == Directory.DEFAULT
325 || mSelectedContactDirectoryId == Directory.LOCAL_INVISIBLE)) {
326 long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
327 if (contactId == mSelectedContactId) {
328 offset = cursor.getPosition();
329 break;
330 }
331 }
332 }
333 if (offset == -1) {
334 return -1;
335 }
336
337 int position = getPositionForPartition(partitionIndex) + offset;
338 if (hasHeader(partitionIndex)) {
339 position++;
340 }
341 return position;
342 }
343
344 public boolean hasValidSelection() {
345 return getSelectedContactPosition() != -1;
346 }
347
348 public Uri getFirstContactUri() {
349 int partitionCount = getPartitionCount();
350 for (int i = 0; i < partitionCount; i++) {
351 DirectoryPartition partition = (DirectoryPartition) getPartition(i);
352 if (partition.isLoading()) {
353 continue;
354 }
355
356 Cursor cursor = getCursor(i);
357 if (cursor == null) {
358 continue;
359 }
360
361 if (!cursor.moveToFirst()) {
362 continue;
363 }
364
365 return getContactUri(i, cursor);
366 }
367
368 return null;
369 }
370
371 @Override
372 public void changeCursor(int partitionIndex, Cursor cursor) {
373 super.changeCursor(partitionIndex, cursor);
374
Wenyi Wang25774d22016-04-08 11:15:11 -0700375 if (cursor == null || !cursor.moveToFirst()) {
376 return;
Chiao Cheng89437e82012-11-01 13:41:51 -0700377 }
Wenyi Wang25774d22016-04-08 11:15:11 -0700378
Wenyi Wangbe88bed2016-05-13 12:04:14 -0700379 if (shouldIncludeFavorites()) {
Wenyi Wang25774d22016-04-08 11:15:11 -0700380 if (cursor.getInt(ContactQuery.CONTACT_STARRED) == 1) {
381 final Set<Integer> favorites = new HashSet<>();
Wenyi Wang25774d22016-04-08 11:15:11 -0700382 favorites.add(cursor.getInt(ContactQuery.CONTACT_ID));
383 while (cursor != null && cursor.moveToNext()) {
384 if (cursor.getInt(ContactQuery.CONTACT_STARRED) != 1
385 || favorites.contains(cursor.getInt(ContactQuery.CONTACT_ID))) {
386 break;
387 }
Wenyi Wanged943d22016-05-12 13:39:12 -0700388 favorites.add(cursor.getInt(ContactQuery.CONTACT_ID));
Wenyi Wang25774d22016-04-08 11:15:11 -0700389 }
Wenyi Wanged943d22016-05-12 13:39:12 -0700390 setFavoritesSectionHeader(favorites.size());
Wenyi Wang25774d22016-04-08 11:15:11 -0700391 }
392 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700393 }
394
395 /**
396 * @return Projection useful for children.
397 */
398 protected final String[] getProjection(boolean forSearch) {
399 final int sortOrder = getContactNameDisplayOrder();
400 if (forSearch) {
Yorke Leeb3d841a2014-07-10 11:38:55 -0700401 if (sortOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700402 return ContactQuery.FILTER_PROJECTION_PRIMARY;
403 } else {
404 return ContactQuery.FILTER_PROJECTION_ALTERNATIVE;
405 }
406 } else {
Yorke Leeb3d841a2014-07-10 11:38:55 -0700407 if (sortOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700408 return ContactQuery.CONTACT_PROJECTION_PRIMARY;
409 } else {
410 return ContactQuery.CONTACT_PROJECTION_ALTERNATIVE;
411 }
412 }
413 }
Tingting Wang0ac73ba2016-07-05 22:33:01 -0700414
415 /**
416 * @return Projection from Data that is useful for children.
417 */
418 protected final String[] getDataProjectionForContacts(boolean forSearch) {
419 final int sortOrder = getContactNameDisplayOrder();
420 if (forSearch) {
421 if (sortOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
422 return replaceFirstString(ContactQuery.FILTER_PROJECTION_PRIMARY);
423 } else {
424 return replaceFirstString(ContactQuery.FILTER_PROJECTION_ALTERNATIVE);
425 }
426 } else {
427 if (sortOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
428 return replaceFirstString(ContactQuery.CONTACT_PROJECTION_PRIMARY);
429 } else {
430 return replaceFirstString(ContactQuery.CONTACT_PROJECTION_ALTERNATIVE);
431 }
432 }
433 }
434
435 /**
436 * @param sourceProjection
437 * @return Replace the first String of sourceProjection with Data.CONTACT_ID.
438 */
439 private String[] replaceFirstString(String[] sourceProjection) {
440 String[] result = sourceProjection.clone();
441 result[0] = Data.CONTACT_ID;
442 return result;
443 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700444}