blob: d459d47cebe6872d3dc6d7f735adf7e9a53a9682 [file] [log] [blame]
Chiao Cheng87a36dc2012-11-07 18:20:17 -08001/*
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 Cheng87a36dc2012-11-07 18:20:17 -080017
18import android.content.ContentUris;
19import android.content.Context;
20import android.content.CursorLoader;
21import android.database.Cursor;
22import android.net.Uri;
23import android.net.Uri.Builder;
24import android.provider.ContactsContract;
25import android.provider.ContactsContract.CommonDataKinds.Callable;
26import android.provider.ContactsContract.CommonDataKinds.Phone;
27import android.provider.ContactsContract.CommonDataKinds.SipAddress;
Chiao Cheng87a36dc2012-11-07 18:20:17 -080028import android.provider.ContactsContract.Contacts;
29import android.provider.ContactsContract.Data;
30import android.provider.ContactsContract.Directory;
Yorke Leee9825a92014-08-14 12:44:17 -070031import android.text.TextUtils;
Chiao Cheng87a36dc2012-11-07 18:20:17 -080032import android.util.Log;
33import android.view.View;
34import android.view.ViewGroup;
35
Gary Mai0a49afa2016-12-05 15:53:58 -080036import com.android.contacts.CallUtil;
37import com.android.contacts.ContactPhotoManager.DefaultImageRequest;
38import com.android.contacts.ContactsUtils;
39import com.android.contacts.GeoUtil;
Arthur Wang3f6a2442016-12-05 14:51:59 -080040import com.android.contacts.R;
Gary Mai69c182a2016-12-05 13:07:03 -080041import com.android.contacts.compat.CallableCompat;
42import com.android.contacts.compat.CompatUtils;
43import com.android.contacts.compat.DirectoryCompat;
44import com.android.contacts.compat.PhoneCompat;
45import com.android.contacts.extensions.ExtendedPhoneDirectoriesManager;
46import com.android.contacts.extensions.ExtensionsFactory;
47import com.android.contacts.preference.ContactsPreferences;
48import com.android.contacts.util.Constants;
Chiao Cheng87a36dc2012-11-07 18:20:17 -080049
Wenyi Wang61f7d092016-02-26 11:03:21 -080050import com.google.common.collect.Lists;
51
Chiao Cheng87a36dc2012-11-07 18:20:17 -080052import java.util.ArrayList;
53import java.util.List;
54
55/**
56 * A cursor adapter for the {@link Phone#CONTENT_ITEM_TYPE} and
57 * {@link SipAddress#CONTENT_ITEM_TYPE}.
58 *
59 * By default this adapter just handles phone numbers. When {@link #setUseCallableUri(boolean)} is
60 * called with "true", this adapter starts handling SIP addresses too, by using {@link Callable}
61 * API instead of {@link Phone}.
62 */
63public class PhoneNumberListAdapter extends ContactEntryListAdapter {
Ben Gilad03a533a2013-08-21 09:46:00 -070064
Chiao Cheng87a36dc2012-11-07 18:20:17 -080065 private static final String TAG = PhoneNumberListAdapter.class.getSimpleName();
66
Tyler Gunn001d9742015-12-18 13:57:02 -080067 public interface Listener {
68 void onVideoCallIconClicked(int position);
69 }
70
Ben Gilad03a533a2013-08-21 09:46:00 -070071 // A list of extended directories to add to the directories from the database
72 private final List<DirectoryPartition> mExtendedDirectories;
73
Wenyi Wangf7899c02016-02-03 17:16:12 -080074 // Extended directories will have ID's that are higher than any of the id's from the database,
75 // so that we can identify them and set them up properly. If no extended directories
Ben Gilad03a533a2013-08-21 09:46:00 -070076 // exist, this will be Long.MAX_VALUE
77 private long mFirstExtendedDirectoryId = Long.MAX_VALUE;
78
Christine Chen3efbe592013-07-08 18:05:03 -070079 public static class PhoneQuery {
Tyler Gunnc4059322015-03-20 09:07:53 -070080
81 /**
82 * Optional key used as part of a JSON lookup key to specify an analytics category
83 * associated with the row.
84 */
85 public static final String ANALYTICS_CATEGORY = "analytics_category";
86
87 /**
88 * Optional key used as part of a JSON lookup key to specify an analytics action associated
89 * with the row.
90 */
91 public static final String ANALYTICS_ACTION = "analytics_action";
92
93 /**
94 * Optional key used as part of a JSON lookup key to specify an analytics value associated
95 * with the row.
96 */
97 public static final String ANALYTICS_VALUE = "analytics_value";
98
Wenyi Wang61f7d092016-02-26 11:03:21 -080099 public static final String[] PROJECTION_PRIMARY_INTERNAL = new String[] {
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800100 Phone._ID, // 0
101 Phone.TYPE, // 1
102 Phone.LABEL, // 2
103 Phone.NUMBER, // 3
104 Phone.CONTACT_ID, // 4
105 Phone.LOOKUP_KEY, // 5
106 Phone.PHOTO_ID, // 6
107 Phone.DISPLAY_NAME_PRIMARY, // 7
Jay Shrauner138515a2013-07-26 09:32:27 -0700108 Phone.PHOTO_THUMBNAIL_URI, // 8
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800109 };
110
Wenyi Wang61f7d092016-02-26 11:03:21 -0800111 public static final String[] PROJECTION_PRIMARY;
112
113 static {
114 final List<String> projectionList = Lists.newArrayList(PROJECTION_PRIMARY_INTERNAL);
115 if (CompatUtils.isMarshmallowCompatible()) {
116 projectionList.add(Phone.CARRIER_PRESENCE); // 9
117 }
118 PROJECTION_PRIMARY = projectionList.toArray(new String[projectionList.size()]);
119 }
120
121 public static final String[] PROJECTION_ALTERNATIVE_INTERNAL = new String[] {
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800122 Phone._ID, // 0
123 Phone.TYPE, // 1
124 Phone.LABEL, // 2
125 Phone.NUMBER, // 3
126 Phone.CONTACT_ID, // 4
127 Phone.LOOKUP_KEY, // 5
128 Phone.PHOTO_ID, // 6
129 Phone.DISPLAY_NAME_ALTERNATIVE, // 7
Jay Shrauner138515a2013-07-26 09:32:27 -0700130 Phone.PHOTO_THUMBNAIL_URI, // 8
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800131 };
132
Wenyi Wang61f7d092016-02-26 11:03:21 -0800133 public static final String[] PROJECTION_ALTERNATIVE;
134
135 static {
136 final List<String> projectionList = Lists.newArrayList(PROJECTION_ALTERNATIVE_INTERNAL);
137 if (CompatUtils.isMarshmallowCompatible()) {
138 projectionList.add(Phone.CARRIER_PRESENCE); // 9
139 }
140 PROJECTION_ALTERNATIVE = projectionList.toArray(new String[projectionList.size()]);
141 }
142
Jay Shrauner138515a2013-07-26 09:32:27 -0700143 public static final int PHONE_ID = 0;
144 public static final int PHONE_TYPE = 1;
145 public static final int PHONE_LABEL = 2;
146 public static final int PHONE_NUMBER = 3;
147 public static final int CONTACT_ID = 4;
148 public static final int LOOKUP_KEY = 5;
149 public static final int PHOTO_ID = 6;
150 public static final int DISPLAY_NAME = 7;
151 public static final int PHOTO_URI = 8;
Tyler Gunn001d9742015-12-18 13:57:02 -0800152 public static final int CARRIER_PRESENCE = 9;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800153 }
154
Yorke Leee9825a92014-08-14 12:44:17 -0700155 private static final String IGNORE_NUMBER_TOO_LONG_CLAUSE =
156 "length(" + Phone.NUMBER + ") < 1000";
157
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800158 private final CharSequence mUnknownNameText;
Andrew Lee32e17da2014-03-31 12:44:34 -0700159 private final String mCountryIso;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800160
161 private ContactListItemView.PhotoPosition mPhotoPosition;
162
163 private boolean mUseCallableUri;
164
Tyler Gunn001d9742015-12-18 13:57:02 -0800165 private Listener mListener;
166
167 private boolean mIsVideoEnabled;
168 private boolean mIsPresenceEnabled;
169
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800170 public PhoneNumberListAdapter(Context context) {
171 super(context);
172 setDefaultFilterHeaderText(R.string.list_filter_phones);
173 mUnknownNameText = context.getText(android.R.string.unknownName);
Andrew Lee32e17da2014-03-31 12:44:34 -0700174 mCountryIso = GeoUtil.getCurrentCountryIso(context);
Ben Gilad03a533a2013-08-21 09:46:00 -0700175
176 final ExtendedPhoneDirectoriesManager manager
177 = ExtensionsFactory.getExtendedPhoneDirectoriesManager();
178 if (manager != null) {
179 mExtendedDirectories = manager.getExtendedDirectories(mContext);
180 } else {
Alon Albert19b692e2013-09-09 14:18:47 -0700181 // Empty list to avoid sticky NPE's
182 mExtendedDirectories = new ArrayList<DirectoryPartition>();
Ben Gilad03a533a2013-08-21 09:46:00 -0700183 }
Tyler Gunn001d9742015-12-18 13:57:02 -0800184
185 int videoCapabilities = CallUtil.getVideoCallingAvailability(context);
186 mIsVideoEnabled = (videoCapabilities & CallUtil.VIDEO_CALLING_ENABLED) != 0;
187 mIsPresenceEnabled = (videoCapabilities & CallUtil.VIDEO_CALLING_PRESENCE) != 0;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800188 }
189
190 protected CharSequence getUnknownNameText() {
191 return mUnknownNameText;
192 }
193
194 @Override
195 public void configureLoader(CursorLoader loader, long directoryId) {
Ben Gilad03a533a2013-08-21 09:46:00 -0700196 String query = getQueryString();
197 if (query == null) {
198 query = "";
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800199 }
Ben Gilad03a533a2013-08-21 09:46:00 -0700200 if (isExtendedDirectory(directoryId)) {
201 final DirectoryPartition directory = getExtendedDirectoryFromId(directoryId);
Alon Albertdd1dcc62013-08-28 14:02:47 -0700202 final String contentUri = directory.getContentUri();
203 if (contentUri == null) {
204 throw new IllegalStateException("Extended directory must have a content URL: "
205 + directory);
206 }
207 final Builder builder = Uri.parse(contentUri).buildUpon();
Ben Gilad03a533a2013-08-21 09:46:00 -0700208 builder.appendPath(query);
209 builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
210 String.valueOf(getDirectoryResultLimit(directory)));
211 loader.setUri(builder.build());
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800212 loader.setProjection(PhoneQuery.PROJECTION_PRIMARY);
213 } else {
Ricky Waif600f072016-04-25 12:13:09 +0100214 final boolean isRemoteDirectoryQuery
215 = DirectoryCompat.isRemoteDirectoryId(directoryId);
Ben Gilad03a533a2013-08-21 09:46:00 -0700216 final Builder builder;
217 if (isSearchMode()) {
218 final Uri baseUri;
219 if (isRemoteDirectoryQuery) {
Ricky Waidffb27d2015-12-08 16:58:27 +0000220 baseUri = PhoneCompat.getContentFilterUri();
Ben Gilad03a533a2013-08-21 09:46:00 -0700221 } else if (mUseCallableUri) {
Ricky Waidffb27d2015-12-08 16:58:27 +0000222 baseUri = CallableCompat.getContentFilterUri();
Ben Gilad03a533a2013-08-21 09:46:00 -0700223 } else {
Ricky Waidffb27d2015-12-08 16:58:27 +0000224 baseUri = PhoneCompat.getContentFilterUri();
Ben Gilad03a533a2013-08-21 09:46:00 -0700225 }
226 builder = baseUri.buildUpon();
227 builder.appendPath(query); // Builder will encode the query
228 builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
229 String.valueOf(directoryId));
230 if (isRemoteDirectoryQuery) {
231 builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
232 String.valueOf(getDirectoryResultLimit(getDirectoryById(directoryId))));
233 }
234 } else {
Wenyi Wangf7899c02016-02-03 17:16:12 -0800235 Uri baseUri = mUseCallableUri ? Callable.CONTENT_URI : Phone.CONTENT_URI;
Ben Gilad03a533a2013-08-21 09:46:00 -0700236 builder = baseUri.buildUpon().appendQueryParameter(
237 ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT));
238 if (isSectionHeaderDisplayEnabled()) {
Yorke Lee9a598eb2014-09-04 14:59:22 -0700239 builder.appendQueryParameter(Phone.EXTRA_ADDRESS_BOOK_INDEX, "true");
Ben Gilad03a533a2013-08-21 09:46:00 -0700240 }
241 applyFilter(loader, builder, directoryId, getFilter());
242 }
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800243
Yorke Leee9825a92014-08-14 12:44:17 -0700244 // Ignore invalid phone numbers that are too long. These can potentially cause freezes
245 // in the UI and there is no reason to display them.
246 final String prevSelection = loader.getSelection();
247 final String newSelection;
248 if (!TextUtils.isEmpty(prevSelection)) {
249 newSelection = prevSelection + " AND " + IGNORE_NUMBER_TOO_LONG_CLAUSE;
250 } else {
251 newSelection = IGNORE_NUMBER_TOO_LONG_CLAUSE;
252 }
253 loader.setSelection(newSelection);
254
Ben Gilad03a533a2013-08-21 09:46:00 -0700255 // Remove duplicates when it is possible.
256 builder.appendQueryParameter(ContactsContract.REMOVE_DUPLICATE_ENTRIES, "true");
257 loader.setUri(builder.build());
258
259 // TODO a projection that includes the search snippet
Yorke Leeb3d841a2014-07-10 11:38:55 -0700260 if (getContactNameDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
Ben Gilad03a533a2013-08-21 09:46:00 -0700261 loader.setProjection(PhoneQuery.PROJECTION_PRIMARY);
262 } else {
263 loader.setProjection(PhoneQuery.PROJECTION_ALTERNATIVE);
264 }
265
Yorke Leeb3d841a2014-07-10 11:38:55 -0700266 if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
Ben Gilad03a533a2013-08-21 09:46:00 -0700267 loader.setSortOrder(Phone.SORT_KEY_PRIMARY);
268 } else {
269 loader.setSortOrder(Phone.SORT_KEY_ALTERNATIVE);
270 }
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800271 }
272 }
273
Jay Shrauner87080702013-08-29 11:35:53 -0700274 protected boolean isExtendedDirectory(long directoryId) {
Ben Gilad03a533a2013-08-21 09:46:00 -0700275 return directoryId >= mFirstExtendedDirectoryId;
276 }
277
278 private DirectoryPartition getExtendedDirectoryFromId(long directoryId) {
279 final int directoryIndex = (int) (directoryId - mFirstExtendedDirectoryId);
280 return mExtendedDirectories.get(directoryIndex);
281 }
282
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800283 /**
284 * Configure {@code loader} and {@code uriBuilder} according to {@code directoryId} and {@code
285 * filter}.
286 */
287 private void applyFilter(CursorLoader loader, Uri.Builder uriBuilder, long directoryId,
288 ContactListFilter filter) {
289 if (filter == null || directoryId != Directory.DEFAULT) {
290 return;
291 }
292
293 final StringBuilder selection = new StringBuilder();
294 final List<String> selectionArgs = new ArrayList<String>();
295
296 switch (filter.filterType) {
297 case ContactListFilter.FILTER_TYPE_CUSTOM: {
298 selection.append(Contacts.IN_VISIBLE_GROUP + "=1");
299 selection.append(" AND " + Contacts.HAS_PHONE_NUMBER + "=1");
300 break;
301 }
302 case ContactListFilter.FILTER_TYPE_ACCOUNT: {
303 filter.addAccountQueryParameterToUrl(uriBuilder);
304 break;
305 }
306 case ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS:
307 case ContactListFilter.FILTER_TYPE_DEFAULT:
308 break; // No selection needed.
309 case ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY:
310 break; // This adapter is always "phone only", so no selection needed either.
311 default:
312 Log.w(TAG, "Unsupported filter type came " +
313 "(type: " + filter.filterType + ", toString: " + filter + ")" +
314 " showing all contacts.");
315 // No selection.
316 break;
317 }
318 loader.setSelection(selection.toString());
319 loader.setSelectionArgs(selectionArgs.toArray(new String[0]));
320 }
321
322 @Override
323 public String getContactDisplayName(int position) {
Jay Shrauner138515a2013-07-26 09:32:27 -0700324 return ((Cursor) getItem(position)).getString(PhoneQuery.DISPLAY_NAME);
325 }
326
327 public String getPhoneNumber(int position) {
328 final Cursor item = (Cursor)getItem(position);
329 return item != null ? item.getString(PhoneQuery.PHONE_NUMBER) : null;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800330 }
331
332 /**
333 * Builds a {@link Data#CONTENT_URI} for the given cursor position.
334 *
335 * @return Uri for the data. may be null if the cursor is not ready.
336 */
337 public Uri getDataUri(int position) {
Jay Shrauner138515a2013-07-26 09:32:27 -0700338 final int partitionIndex = getPartitionForPosition(position);
339 final Cursor item = (Cursor)getItem(position);
340 return item != null ? getDataUri(partitionIndex, item) : null;
341 }
342
343 public Uri getDataUri(int partitionIndex, Cursor cursor) {
Alon Albert368cae82013-09-03 12:31:31 -0700344 final long directoryId =
345 ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
Ricky Waif600f072016-04-25 12:13:09 +0100346 if (DirectoryCompat.isRemoteDirectoryId(directoryId)) {
Victor Chang3e21eef2015-12-24 13:17:42 +0800347 return null;
348 } else if (DirectoryCompat.isEnterpriseDirectoryId(directoryId)) {
349 /*
350 * ContentUris.withAppendedId(Data.CONTENT_URI, phoneId), is invalid if
351 * isEnterpriseDirectoryId returns true, because the uri itself will fail since the
352 * ContactsProvider in Android Framework currently doesn't support it. return null until
353 * Android framework has enterprise version of Data.CONTENT_URI
354 */
355 return null;
356 } else {
Jay Shrauner138515a2013-07-26 09:32:27 -0700357 final long phoneId = cursor.getLong(PhoneQuery.PHONE_ID);
358 return ContentUris.withAppendedId(Data.CONTENT_URI, phoneId);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800359 }
360 }
361
Tyler Gunnc4059322015-03-20 09:07:53 -0700362 /**
363 * Retrieves the lookup key for the given cursor position.
364 *
365 * @param position The cursor position.
366 * @return The lookup key.
367 */
368 public String getLookupKey(int position) {
369 final Cursor item = (Cursor)getItem(position);
370 return item != null ? item.getString(PhoneQuery.LOOKUP_KEY) : null;
371 }
372
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800373 @Override
Andrew Lee020ba622014-04-25 15:26:35 -0700374 protected ContactListItemView newView(
375 Context context, int partition, Cursor cursor, int position, ViewGroup parent) {
376 ContactListItemView view = super.newView(context, partition, cursor, position, parent);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800377 view.setUnknownNameText(mUnknownNameText);
378 view.setQuickContactEnabled(isQuickContactEnabled());
379 view.setPhotoPosition(mPhotoPosition);
380 return view;
381 }
382
Christine Chen3efbe592013-07-08 18:05:03 -0700383 protected void setHighlight(ContactListItemView view, Cursor cursor) {
384 view.setHighlightedPrefix(isSearchMode() ? getUpperCaseQueryString() : null);
385 }
386
Jay Shrauner29884ed2013-08-27 15:57:52 -0700387 // Override default, which would return number of phone numbers, so we
388 // instead return number of contacts.
389 @Override
390 protected int getResultCount(Cursor cursor) {
391 if (cursor == null) {
392 return 0;
393 }
394 cursor.moveToPosition(-1);
395 long curContactId = -1;
396 int numContacts = 0;
397 while(cursor.moveToNext()) {
398 final long contactId = cursor.getLong(PhoneQuery.CONTACT_ID);
399 if (contactId != curContactId) {
400 curContactId = contactId;
401 ++numContacts;
402 }
403 }
404 return numContacts;
405 }
406
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800407 @Override
408 protected void bindView(View itemView, int partition, Cursor cursor, int position) {
Brian Attwella5ad5572014-09-15 11:18:03 -0700409 super.bindView(itemView, partition, cursor, position);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800410 ContactListItemView view = (ContactListItemView)itemView;
411
Christine Chen3efbe592013-07-08 18:05:03 -0700412 setHighlight(view, cursor);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800413
414 // Look at elements before and after this position, checking if contact IDs are same.
415 // If they have one same contact ID, it means they can be grouped.
416 //
417 // In one group, only the first entry will show its photo and its name, and the other
418 // entries in the group show just their data (e.g. phone number, email address).
419 cursor.moveToPosition(position);
420 boolean isFirstEntry = true;
421 boolean showBottomDivider = true;
Jay Shrauner138515a2013-07-26 09:32:27 -0700422 final long currentContactId = cursor.getLong(PhoneQuery.CONTACT_ID);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800423 if (cursor.moveToPrevious() && !cursor.isBeforeFirst()) {
Jay Shrauner138515a2013-07-26 09:32:27 -0700424 final long previousContactId = cursor.getLong(PhoneQuery.CONTACT_ID);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800425 if (currentContactId == previousContactId) {
426 isFirstEntry = false;
427 }
428 }
429 cursor.moveToPosition(position);
430 if (cursor.moveToNext() && !cursor.isAfterLast()) {
Jay Shrauner138515a2013-07-26 09:32:27 -0700431 final long nextContactId = cursor.getLong(PhoneQuery.CONTACT_ID);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800432 if (currentContactId == nextContactId) {
433 // The following entry should be in the same group, which means we don't want a
434 // divider between them.
435 // TODO: we want a different divider than the divider between groups. Just hiding
436 // this divider won't be enough.
437 showBottomDivider = false;
438 }
439 }
440 cursor.moveToPosition(position);
441
Brian Attwell2ea151c2014-09-03 19:53:26 -0700442 bindViewId(view, cursor, PhoneQuery.PHONE_ID);
443
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800444 bindSectionHeaderAndDivider(view, position);
445 if (isFirstEntry) {
446 bindName(view, cursor);
447 if (isQuickContactEnabled()) {
Jay Shrauner138515a2013-07-26 09:32:27 -0700448 bindQuickContact(view, partition, cursor, PhoneQuery.PHOTO_ID,
449 PhoneQuery.PHOTO_URI, PhoneQuery.CONTACT_ID,
Yorke Lee9df5e192014-02-12 14:58:25 -0800450 PhoneQuery.LOOKUP_KEY, PhoneQuery.DISPLAY_NAME);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800451 } else {
Jay Shrauner138515a2013-07-26 09:32:27 -0700452 if (getDisplayPhotos()) {
453 bindPhoto(view, partition, cursor);
454 }
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800455 }
456 } else {
457 unbindName(view);
458
459 view.removePhotoView(true, false);
460 }
Alon Albert368cae82013-09-03 12:31:31 -0700461
462 final DirectoryPartition directory = (DirectoryPartition) getPartition(partition);
Tyler Gunn001d9742015-12-18 13:57:02 -0800463 bindPhoneNumber(view, cursor, directory.isDisplayNumber(), position);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800464 }
465
Tyler Gunn001d9742015-12-18 13:57:02 -0800466 protected void bindPhoneNumber(ContactListItemView view, Cursor cursor, boolean displayNumber,
467 int position) {
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800468 CharSequence label = null;
Alon Albert368cae82013-09-03 12:31:31 -0700469 if (displayNumber && !cursor.isNull(PhoneQuery.PHONE_TYPE)) {
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800470 final int type = cursor.getInt(PhoneQuery.PHONE_TYPE);
471 final String customLabel = cursor.getString(PhoneQuery.PHONE_LABEL);
472
473 // TODO cache
474 label = Phone.getTypeLabel(getContext().getResources(), type, customLabel);
475 }
476 view.setLabel(label);
Alon Albert368cae82013-09-03 12:31:31 -0700477 final String text;
478 if (displayNumber) {
479 text = cursor.getString(PhoneQuery.PHONE_NUMBER);
480 } else {
481 // Display phone label. If that's null, display geocoded location for the number
482 final String phoneLabel = cursor.getString(PhoneQuery.PHONE_LABEL);
483 if (phoneLabel != null) {
484 text = phoneLabel;
485 } else {
486 final String phoneNumber = cursor.getString(PhoneQuery.PHONE_NUMBER);
487 text = GeoUtil.getGeocodedLocationFor(mContext, phoneNumber);
488 }
489 }
Andrew Lee32e17da2014-03-31 12:44:34 -0700490 view.setPhoneNumber(text, mCountryIso);
Tyler Gunn001d9742015-12-18 13:57:02 -0800491
Wenyi Wang61f7d092016-02-26 11:03:21 -0800492 if (CompatUtils.isVideoCompatible()) {
493 // Determine if carrier presence indicates the number supports video calling.
494 int carrierPresence = cursor.getInt(PhoneQuery.CARRIER_PRESENCE);
495 boolean isPresent = (carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) != 0;
Tyler Gunn001d9742015-12-18 13:57:02 -0800496
Wenyi Wang61f7d092016-02-26 11:03:21 -0800497 boolean isVideoIconShown = mIsVideoEnabled && (
498 mIsPresenceEnabled && isPresent || !mIsPresenceEnabled);
499 view.setShowVideoCallIcon(isVideoIconShown, mListener, position);
500 }
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800501 }
502
503 protected void bindSectionHeaderAndDivider(final ContactListItemView view, int position) {
504 if (isSectionHeaderDisplayEnabled()) {
505 Placement placement = getItemPlacementInSection(position);
506 view.setSectionHeader(placement.firstInSection ? placement.sectionHeader : null);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800507 } else {
508 view.setSectionHeader(null);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800509 }
510 }
511
512 protected void bindName(final ContactListItemView view, Cursor cursor) {
Jay Shrauner138515a2013-07-26 09:32:27 -0700513 view.showDisplayName(cursor, PhoneQuery.DISPLAY_NAME, getContactNameDisplayOrder());
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800514 // Note: we don't show phonetic names any more (see issue 5265330)
515 }
516
517 protected void unbindName(final ContactListItemView view) {
518 view.hideDisplayName();
519 }
520
Tony Mak6473f8f2016-02-23 20:39:16 +0000521 @Override
522 protected void bindWorkProfileIcon(final ContactListItemView view, int partition) {
Victor Chang3e21eef2015-12-24 13:17:42 +0800523 final DirectoryPartition directory = (DirectoryPartition) getPartition(partition);
524 final long directoryId = directory.getDirectoryId();
525 final long userType = ContactsUtils.determineUserType(directoryId, null);
Victor Chang0286a942016-01-25 17:45:27 +0000526 // Work directory must not be a extended directory. An extended directory is custom
527 // directory in the app, but not a directory provided by framework. So it can't be
528 // USER_TYPE_WORK.
529 view.setWorkProfileIconEnabled(
530 !isExtendedDirectory(directoryId) && userType == ContactsUtils.USER_TYPE_WORK);
Victor Chang3e21eef2015-12-24 13:17:42 +0800531 }
532
Jay Shrauner138515a2013-07-26 09:32:27 -0700533 protected void bindPhoto(final ContactListItemView view, int partitionIndex, Cursor cursor) {
534 if (!isPhotoSupported(partitionIndex)) {
535 view.removePhotoView();
536 return;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800537 }
538
Jay Shrauner138515a2013-07-26 09:32:27 -0700539 long photoId = 0;
540 if (!cursor.isNull(PhoneQuery.PHOTO_ID)) {
541 photoId = cursor.getLong(PhoneQuery.PHOTO_ID);
542 }
543
544 if (photoId != 0) {
Yorke Leec4a2a232014-04-28 17:53:42 -0700545 getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false,
546 getCircularPhotos(), null);
Jay Shrauner138515a2013-07-26 09:32:27 -0700547 } else {
548 final String photoUriString = cursor.getString(PhoneQuery.PHOTO_URI);
549 final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
Yorke Lee9df5e192014-02-12 14:58:25 -0800550
551 DefaultImageRequest request = null;
552 if (photoUri == null) {
553 final String displayName = cursor.getString(PhoneQuery.DISPLAY_NAME);
554 final String lookupKey = cursor.getString(PhoneQuery.LOOKUP_KEY);
Brian Attwellab6f8c42014-07-09 22:03:22 -0700555 request = new DefaultImageRequest(displayName, lookupKey, getCircularPhotos());
Yorke Lee9df5e192014-02-12 14:58:25 -0800556 }
Yorke Leec4a2a232014-04-28 17:53:42 -0700557 getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false,
558 getCircularPhotos(), request);
Jay Shrauner138515a2013-07-26 09:32:27 -0700559 }
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800560 }
561
562 public void setPhotoPosition(ContactListItemView.PhotoPosition photoPosition) {
563 mPhotoPosition = photoPosition;
564 }
565
566 public ContactListItemView.PhotoPosition getPhotoPosition() {
567 return mPhotoPosition;
568 }
569
570 public void setUseCallableUri(boolean useCallableUri) {
571 mUseCallableUri = useCallableUri;
572 }
573
574 public boolean usesCallableUri() {
575 return mUseCallableUri;
576 }
Ben Gilad03a533a2013-08-21 09:46:00 -0700577
578 /**
579 * Override base implementation to inject extended directories between local & remote
580 * directories. This is done in the following steps:
581 * 1. Call base implementation to add directories from the cursor.
582 * 2. Iterate all base directories and establish the following information:
583 * a. The highest directory id so that we can assign unused id's to the extended directories.
584 * b. The index of the last non-remote directory. This is where we will insert extended
585 * directories.
586 * 3. Iterate the extended directories and for each one, assign an ID and insert it in the
587 * proper location.
588 */
589 @Override
590 public void changeDirectories(Cursor cursor) {
591 super.changeDirectories(cursor);
592 if (getDirectorySearchMode() == DirectoryListLoader.SEARCH_MODE_NONE) {
593 return;
594 }
595 final int numExtendedDirectories = mExtendedDirectories.size();
596 if (getPartitionCount() == cursor.getCount() + numExtendedDirectories) {
597 // already added all directories;
598 return;
599 }
600 //
601 mFirstExtendedDirectoryId = Long.MAX_VALUE;
602 if (numExtendedDirectories > 0) {
603 // The Directory.LOCAL_INVISIBLE is not in the cursor but we can't reuse it's
604 // "special" ID.
605 long maxId = Directory.LOCAL_INVISIBLE;
606 int insertIndex = 0;
607 for (int i = 0, n = getPartitionCount(); i < n; i++) {
608 final DirectoryPartition partition = (DirectoryPartition) getPartition(i);
609 final long id = partition.getDirectoryId();
610 if (id > maxId) {
611 maxId = id;
612 }
Ricky Waif600f072016-04-25 12:13:09 +0100613 if (!DirectoryCompat.isRemoteDirectoryId(id)) {
Ben Gilad03a533a2013-08-21 09:46:00 -0700614 // assuming remote directories come after local, we will end up with the index
615 // where we should insert extended directories. This also works if there are no
616 // remote directories at all.
617 insertIndex = i + 1;
618 }
619 }
620 // Extended directories ID's cannot collide with base directories
621 mFirstExtendedDirectoryId = maxId + 1;
622 for (int i = 0; i < numExtendedDirectories; i++) {
623 final long id = mFirstExtendedDirectoryId + i;
624 final DirectoryPartition directory = mExtendedDirectories.get(i);
625 if (getPartitionByDirectoryId(id) == -1) {
626 addPartition(insertIndex, directory);
627 directory.setDirectoryId(id);
628 }
629 }
630 }
631 }
Alon Albert20303432013-09-04 15:20:20 -0700632
Victor Changb83af232015-12-01 19:13:12 +0000633 @Override
Alon Albert20303432013-09-04 15:20:20 -0700634 protected Uri getContactUri(int partitionIndex, Cursor cursor,
635 int contactIdColumn, int lookUpKeyColumn) {
636 final DirectoryPartition directory = (DirectoryPartition) getPartition(partitionIndex);
637 final long directoryId = directory.getDirectoryId();
638 if (!isExtendedDirectory(directoryId)) {
639 return super.getContactUri(partitionIndex, cursor, contactIdColumn, lookUpKeyColumn);
640 }
Alon Albert20303432013-09-04 15:20:20 -0700641 return Contacts.CONTENT_LOOKUP_URI.buildUpon()
642 .appendPath(Constants.LOOKUP_URI_ENCODED)
Alon Albert20303432013-09-04 15:20:20 -0700643 .appendQueryParameter(Directory.DISPLAY_NAME, directory.getLabel())
644 .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
645 String.valueOf(directoryId))
Yorke Lee208f2202013-10-08 10:26:24 -0700646 .encodedFragment(cursor.getString(lookUpKeyColumn))
Alon Albert20303432013-09-04 15:20:20 -0700647 .build();
648 }
Tyler Gunn001d9742015-12-18 13:57:02 -0800649
650 public Listener getListener() {
651 return mListener;
652 }
653
654 public void setListener(Listener listener) {
655 mListener = listener;
656 }
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800657}