blob: 58279307893eb029f21ad93a61499942c65d73dd [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 */
16package com.android.contacts.common.list;
17
18import android.content.ContentUris;
19import android.content.Context;
20import android.content.CursorLoader;
21import android.content.SharedPreferences;
22import android.database.Cursor;
23import android.net.Uri;
24import android.net.Uri.Builder;
25import android.preference.PreferenceManager;
26import android.provider.ContactsContract;
27import android.provider.ContactsContract.Contacts;
Tingting Wang0ac73ba2016-07-05 22:33:01 -070028import android.provider.ContactsContract.Data;
Chiao Cheng89437e82012-11-01 13:41:51 -070029import android.provider.ContactsContract.Directory;
Yorke Leea12a04c2014-07-01 10:39:42 -070030import android.provider.ContactsContract.SearchSnippets;
Chiao Cheng89437e82012-11-01 13:41:51 -070031import android.text.TextUtils;
32import android.view.View;
33
Walter Jangfddbb832016-08-16 13:12:27 -070034import com.android.contacts.common.Experiments;
Ricky Waidffb27d2015-12-08 16:58:27 +000035import com.android.contacts.common.compat.ContactsCompat;
Tingting Wang0ac73ba2016-07-05 22:33:01 -070036import com.android.contacts.common.model.account.AccountWithDataSet;
Chiao Cheng89437e82012-11-01 13:41:51 -070037import com.android.contacts.common.preference.ContactsPreferences;
Walter Jange837ae32016-08-15 12:50:37 -070038import com.android.contactsbind.experiments.Flags;
Chiao Cheng89437e82012-11-01 13:41:51 -070039
40import java.util.ArrayList;
41import java.util.List;
42
43/**
44 * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
45 */
46public class DefaultContactListAdapter extends ContactListAdapter {
47
Yorke Leeaed0a9e2014-07-08 08:03:56 -070048 public static final char SNIPPET_START_MATCH = '[';
49 public static final char SNIPPET_END_MATCH = ']';
Chiao Cheng89437e82012-11-01 13:41:51 -070050
Walter Jang5a8f91c2016-03-30 11:21:55 -070051 // Contacts contacted within the last 3 days (in seconds)
52 private static final long LAST_TIME_USED_3_DAYS_SEC = 3L * 24 * 60 * 60;
53
54 // Contacts contacted within the last 7 days (in seconds)
55 private static final long LAST_TIME_USED_7_DAYS_SEC = 7L * 24 * 60 * 60;
56
57 // Contacts contacted within the last 14 days (in seconds)
58 private static final long LAST_TIME_USED_14_DAYS_SEC = 14L * 24 * 60 * 60;
59
60 // Contacts contacted within the last 30 days (in seconds)
61 private static final long LAST_TIME_USED_30_DAYS_SEC = 30L * 24 * 60 * 60;
62
63 private static final String TIME_SINCE_LAST_USED_SEC =
64 "(strftime('%s', 'now') - " + Contacts.LAST_TIME_CONTACTED + "/1000)";
65
66 private static final String STREQUENT_SORT =
67 "(CASE WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_3_DAYS_SEC +
68 " THEN 0 " +
69 " WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_7_DAYS_SEC +
70 " THEN 1 " +
71 " WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_14_DAYS_SEC +
72 " THEN 2 " +
73 " WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_30_DAYS_SEC +
74 " THEN 3 " +
75 " ELSE 4 END), " +
76 Contacts.TIMES_CONTACTED + " DESC, " +
77 Contacts.STARRED + " DESC";
Walter Jang64743832016-03-17 21:01:07 -070078
Chiao Cheng89437e82012-11-01 13:41:51 -070079 public DefaultContactListAdapter(Context context) {
80 super(context);
81 }
82
83 @Override
84 public void configureLoader(CursorLoader loader, long directoryId) {
Wenyi Wangbe88bed2016-05-13 12:04:14 -070085 if (loader instanceof FavoritesAndContactsLoader) {
86 ((FavoritesAndContactsLoader) loader).setLoadFavorites(shouldIncludeFavorites());
Chiao Cheng89437e82012-11-01 13:41:51 -070087 }
88
Walter Jang1f828612016-02-09 16:15:37 -080089 String sortOrder = null;
Chiao Cheng89437e82012-11-01 13:41:51 -070090 if (isSearchMode()) {
91 String query = getQueryString();
Walter Jang1f828612016-02-09 16:15:37 -080092 if (query == null) query = "";
Chiao Cheng89437e82012-11-01 13:41:51 -070093 query = query.trim();
94 if (TextUtils.isEmpty(query)) {
95 // Regardless of the directory, we don't want anything returned,
96 // so let's just send a "nothing" query to the local directory.
97 loader.setUri(Contacts.CONTENT_URI);
98 loader.setProjection(getProjection(false));
99 loader.setSelection("0");
Walter Jang08379b12016-06-14 15:35:28 -0700100 } else if (isGroupMembersFilter()) {
101 final ContactListFilter filter = getFilter();
102 configureUri(loader, directoryId, filter);
Walter Jang1842e142016-03-31 09:03:18 -0700103 // TODO: This is not the normal type to filter URI so we load the non-search
104 // projection. Consider creating a specific group member adapter to make it more
105 // clear.
Walter Jang08379b12016-06-14 15:35:28 -0700106 loader.setProjection(getProjection(/* forSearch */ false));
107 loader.setSelection(
108 Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?1 OR " +
109 Contacts.DISPLAY_NAME_ALTERNATIVE + " LIKE ?1");
110 final String[] args = new String[1];
111 args[0] = query + "%";
112 loader.setSelectionArgs(args);
Chiao Cheng89437e82012-11-01 13:41:51 -0700113 } else {
Ricky Waidffb27d2015-12-08 16:58:27 +0000114 final Builder builder = ContactsCompat.getContentUri().buildUpon();
Walter Jang54564402016-01-18 11:56:19 -0800115 appendSearchParameters(builder, query, directoryId);
Chiao Cheng89437e82012-11-01 13:41:51 -0700116 loader.setUri(builder.build());
117 loader.setProjection(getProjection(true));
Walter Jangc4626532016-06-21 14:06:51 -0700118 sortOrder = STREQUENT_SORT;
Chiao Cheng89437e82012-11-01 13:41:51 -0700119 }
120 } else {
Walter Jang1f828612016-02-09 16:15:37 -0800121 final ContactListFilter filter = getFilter();
Chiao Cheng89437e82012-11-01 13:41:51 -0700122 configureUri(loader, directoryId, filter);
Tingting Wang0ac73ba2016-07-05 22:33:01 -0700123 if (filter != null
124 && filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
125 loader.setProjection(getDataProjectionForContacts(false));
126 } else {
127 loader.setProjection(getProjection(false));
128 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700129 configureSelection(loader, directoryId, filter);
130 }
131
Yorke Leeb3d841a2014-07-10 11:38:55 -0700132 if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
Walter Jang1f828612016-02-09 16:15:37 -0800133 if (sortOrder == null) {
134 sortOrder = Contacts.SORT_KEY_PRIMARY;
135 } else {
136 sortOrder += ", " + Contacts.SORT_KEY_PRIMARY;
137 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700138 } else {
Walter Jang1f828612016-02-09 16:15:37 -0800139 if (sortOrder == null) {
140 sortOrder = Contacts.SORT_KEY_ALTERNATIVE;
141 } else {
142 sortOrder += ", " + Contacts.SORT_KEY_ALTERNATIVE;
143 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700144 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700145 loader.setSortOrder(sortOrder);
146 }
147
Walter Jang08379b12016-06-14 15:35:28 -0700148 private boolean isGroupMembersFilter() {
149 final ContactListFilter filter = getFilter();
150 return filter != null && filter.filterType == ContactListFilter.FILTER_TYPE_GROUP_MEMBERS;
151 }
152
Walter Jang54564402016-01-18 11:56:19 -0800153 private void appendSearchParameters(Builder builder, String query, long directoryId) {
154 builder.appendPath(query); // Builder will encode the query
155 builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
156 String.valueOf(directoryId));
157 if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE) {
158 builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
159 String.valueOf(getDirectoryResultLimit(getDirectoryById(directoryId))));
160 }
Walter Jang8f012642016-02-12 17:43:27 -0800161 builder.appendQueryParameter(SearchSnippets.DEFERRED_SNIPPETING_KEY, "1");
Walter Jang54564402016-01-18 11:56:19 -0800162 }
163
Chiao Cheng89437e82012-11-01 13:41:51 -0700164 protected void configureUri(CursorLoader loader, long directoryId, ContactListFilter filter) {
165 Uri uri = Contacts.CONTENT_URI;
Tingting Wang0ac73ba2016-07-05 22:33:01 -0700166 if (filter != null) {
167 if (filter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
168 String lookupKey = getSelectedContactLookupKey();
169 if (lookupKey != null) {
170 uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
171 } else {
172 uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, getSelectedContactId());
173 }
174 } else if (filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
175 uri = Data.CONTENT_URI;
Chiao Cheng89437e82012-11-01 13:41:51 -0700176 }
177 }
178
179 if (directoryId == Directory.DEFAULT && isSectionHeaderDisplayEnabled()) {
180 uri = ContactListAdapter.buildSectionIndexerUri(uri);
181 }
182
183 // The "All accounts" filter is the same as the entire contents of Directory.DEFAULT
184 if (filter != null
185 && filter.filterType != ContactListFilter.FILTER_TYPE_CUSTOM
186 && filter.filterType != ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
187 final Uri.Builder builder = uri.buildUpon();
188 builder.appendQueryParameter(
189 ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT));
Walter Jang08379b12016-06-14 15:35:28 -0700190 if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
191 || filter.filterType == ContactListFilter.FILTER_TYPE_GROUP_MEMBERS) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700192 filter.addAccountQueryParameterToUrl(builder);
193 }
194 uri = builder.build();
195 }
196
197 loader.setUri(uri);
198 }
199
200 private void configureSelection(
201 CursorLoader loader, long directoryId, ContactListFilter filter) {
202 if (filter == null) {
203 return;
204 }
205
206 if (directoryId != Directory.DEFAULT) {
207 return;
208 }
209
210 StringBuilder selection = new StringBuilder();
211 List<String> selectionArgs = new ArrayList<String>();
212
213 switch (filter.filterType) {
214 case ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS: {
215 // We have already added directory=0 to the URI, which takes care of this
216 // filter
217 break;
218 }
219 case ContactListFilter.FILTER_TYPE_SINGLE_CONTACT: {
220 // We have already added the lookup key to the URI, which takes care of this
221 // filter
222 break;
223 }
224 case ContactListFilter.FILTER_TYPE_STARRED: {
225 selection.append(Contacts.STARRED + "!=0");
226 break;
227 }
228 case ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY: {
229 selection.append(Contacts.HAS_PHONE_NUMBER + "=1");
230 break;
231 }
232 case ContactListFilter.FILTER_TYPE_CUSTOM: {
233 selection.append(Contacts.IN_VISIBLE_GROUP + "=1");
234 if (isCustomFilterForPhoneNumbersOnly()) {
235 selection.append(" AND " + Contacts.HAS_PHONE_NUMBER + "=1");
236 }
237 break;
238 }
239 case ContactListFilter.FILTER_TYPE_ACCOUNT: {
240 // We use query parameters for account filter, so no selection to add here.
241 break;
242 }
Walter Jang08379b12016-06-14 15:35:28 -0700243 case ContactListFilter.FILTER_TYPE_GROUP_MEMBERS: {
Walter Jang08379b12016-06-14 15:35:28 -0700244 break;
245 }
Tingting Wang0ac73ba2016-07-05 22:33:01 -0700246 case ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS: {
Marcus Hagerott6caf23f2016-08-18 15:02:42 -0700247 if (filter.accountType != null) {
248 selection.append(ContactsContract.RawContacts.ACCOUNT_TYPE)
249 .append("=?");
250 selectionArgs.add(filter.accountType);
251 if (filter.accountName != null) {
252 selection.append(" AND ").append(ContactsContract.RawContacts.ACCOUNT_NAME)
253 .append(("=?"));
254 selectionArgs.add(filter.accountName);
255 }
256 } else {
257 selection.append(AccountWithDataSet.LOCAL_ACCOUNT_SELECTION);
258 }
Tingting Wang0ac73ba2016-07-05 22:33:01 -0700259 break;
260 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700261 }
262 loader.setSelection(selection.toString());
263 loader.setSelectionArgs(selectionArgs.toArray(new String[0]));
264 }
265
266 @Override
267 protected void bindView(View itemView, int partition, Cursor cursor, int position) {
Brian Attwella5ad5572014-09-15 11:18:03 -0700268 super.bindView(itemView, partition, cursor, position);
Chiao Cheng89437e82012-11-01 13:41:51 -0700269 final ContactListItemView view = (ContactListItemView)itemView;
270
271 view.setHighlightedPrefix(isSearchMode() ? getUpperCaseQueryString() : null);
272
273 if (isSelectionVisible()) {
274 view.setActivated(isSelectedContact(partition, cursor));
275 }
276
277 bindSectionHeaderAndDivider(view, position, cursor);
278
279 if (isQuickContactEnabled()) {
280 bindQuickContact(view, partition, cursor, ContactQuery.CONTACT_PHOTO_ID,
281 ContactQuery.CONTACT_PHOTO_URI, ContactQuery.CONTACT_ID,
Yorke Lee9df5e192014-02-12 14:58:25 -0800282 ContactQuery.CONTACT_LOOKUP_KEY, ContactQuery.CONTACT_DISPLAY_NAME);
Chiao Cheng89437e82012-11-01 13:41:51 -0700283 } else {
284 if (getDisplayPhotos()) {
285 bindPhoto(view, partition, cursor);
286 }
287 }
288
Brian Attwell2ea151c2014-09-03 19:53:26 -0700289 bindNameAndViewId(view, cursor);
Chiao Cheng89437e82012-11-01 13:41:51 -0700290 bindPresenceAndStatusMessage(view, cursor);
291
292 if (isSearchMode()) {
293 bindSearchSnippet(view, cursor);
294 } else {
295 view.setSnippet(null);
296 }
297 }
298
299 private boolean isCustomFilterForPhoneNumbersOnly() {
300 // TODO: this flag should not be stored in shared prefs. It needs to be in the db.
301 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
302 return prefs.getBoolean(ContactsPreferences.PREF_DISPLAY_ONLY_PHONES,
303 ContactsPreferences.PREF_DISPLAY_ONLY_PHONES_DEFAULT);
304 }
305}