blob: d8344c6d609c82bed7196bbec1b24bac4be33fa2 [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.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 Jangf61f0ee2016-11-29 18:40:17 +000034import com.android.contacts.Experiments;
Gary Mai69c182a2016-12-05 13:07:03 -080035import com.android.contacts.compat.ContactsCompat;
36import com.android.contacts.model.account.AccountWithDataSet;
37import com.android.contacts.preference.ContactsPreferences;
Walter Jangf61f0ee2016-11-29 18:40:17 +000038import 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;
Walter Jangf61f0ee2016-11-29 18:40:17 +0000119 if (Flags.getInstance().getBoolean(Experiments.SEARCH_YENTA)
120 && loader instanceof FavoritesAndContactsLoader
121 && directoryId == Directory.DEFAULT) {
122 final FavoritesAndContactsLoader favoritesAndContactsLoader =
123 (FavoritesAndContactsLoader) loader;
124 favoritesAndContactsLoader.setAutocompleteQuery(query);
125 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700126 }
127 } else {
Walter Jang1f828612016-02-09 16:15:37 -0800128 final ContactListFilter filter = getFilter();
Chiao Cheng89437e82012-11-01 13:41:51 -0700129 configureUri(loader, directoryId, filter);
Tingting Wang0ac73ba2016-07-05 22:33:01 -0700130 if (filter != null
131 && filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
132 loader.setProjection(getDataProjectionForContacts(false));
133 } else {
134 loader.setProjection(getProjection(false));
135 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700136 configureSelection(loader, directoryId, filter);
137 }
138
Yorke Leeb3d841a2014-07-10 11:38:55 -0700139 if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
Walter Jang1f828612016-02-09 16:15:37 -0800140 if (sortOrder == null) {
141 sortOrder = Contacts.SORT_KEY_PRIMARY;
142 } else {
143 sortOrder += ", " + Contacts.SORT_KEY_PRIMARY;
144 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700145 } else {
Walter Jang1f828612016-02-09 16:15:37 -0800146 if (sortOrder == null) {
147 sortOrder = Contacts.SORT_KEY_ALTERNATIVE;
148 } else {
149 sortOrder += ", " + Contacts.SORT_KEY_ALTERNATIVE;
150 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700151 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700152 loader.setSortOrder(sortOrder);
153 }
154
Walter Jang08379b12016-06-14 15:35:28 -0700155 private boolean isGroupMembersFilter() {
156 final ContactListFilter filter = getFilter();
157 return filter != null && filter.filterType == ContactListFilter.FILTER_TYPE_GROUP_MEMBERS;
158 }
159
Walter Jang54564402016-01-18 11:56:19 -0800160 private void appendSearchParameters(Builder builder, String query, long directoryId) {
161 builder.appendPath(query); // Builder will encode the query
162 builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
163 String.valueOf(directoryId));
164 if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE) {
165 builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
166 String.valueOf(getDirectoryResultLimit(getDirectoryById(directoryId))));
167 }
Walter Jang8f012642016-02-12 17:43:27 -0800168 builder.appendQueryParameter(SearchSnippets.DEFERRED_SNIPPETING_KEY, "1");
Walter Jang54564402016-01-18 11:56:19 -0800169 }
170
Chiao Cheng89437e82012-11-01 13:41:51 -0700171 protected void configureUri(CursorLoader loader, long directoryId, ContactListFilter filter) {
172 Uri uri = Contacts.CONTENT_URI;
Tingting Wang0ac73ba2016-07-05 22:33:01 -0700173 if (filter != null) {
174 if (filter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
175 String lookupKey = getSelectedContactLookupKey();
176 if (lookupKey != null) {
177 uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
178 } else {
179 uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, getSelectedContactId());
180 }
181 } else if (filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
182 uri = Data.CONTENT_URI;
Chiao Cheng89437e82012-11-01 13:41:51 -0700183 }
184 }
185
186 if (directoryId == Directory.DEFAULT && isSectionHeaderDisplayEnabled()) {
187 uri = ContactListAdapter.buildSectionIndexerUri(uri);
188 }
189
190 // The "All accounts" filter is the same as the entire contents of Directory.DEFAULT
191 if (filter != null
192 && filter.filterType != ContactListFilter.FILTER_TYPE_CUSTOM
193 && filter.filterType != ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
194 final Uri.Builder builder = uri.buildUpon();
195 builder.appendQueryParameter(
196 ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT));
Walter Jang08379b12016-06-14 15:35:28 -0700197 if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
198 || filter.filterType == ContactListFilter.FILTER_TYPE_GROUP_MEMBERS) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700199 filter.addAccountQueryParameterToUrl(builder);
200 }
201 uri = builder.build();
202 }
203
204 loader.setUri(uri);
205 }
206
207 private void configureSelection(
208 CursorLoader loader, long directoryId, ContactListFilter filter) {
209 if (filter == null) {
210 return;
211 }
212
213 if (directoryId != Directory.DEFAULT) {
214 return;
215 }
216
217 StringBuilder selection = new StringBuilder();
218 List<String> selectionArgs = new ArrayList<String>();
219
220 switch (filter.filterType) {
221 case ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS: {
222 // We have already added directory=0 to the URI, which takes care of this
223 // filter
224 break;
225 }
226 case ContactListFilter.FILTER_TYPE_SINGLE_CONTACT: {
227 // We have already added the lookup key to the URI, which takes care of this
228 // filter
229 break;
230 }
231 case ContactListFilter.FILTER_TYPE_STARRED: {
232 selection.append(Contacts.STARRED + "!=0");
233 break;
234 }
235 case ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY: {
236 selection.append(Contacts.HAS_PHONE_NUMBER + "=1");
237 break;
238 }
239 case ContactListFilter.FILTER_TYPE_CUSTOM: {
240 selection.append(Contacts.IN_VISIBLE_GROUP + "=1");
241 if (isCustomFilterForPhoneNumbersOnly()) {
242 selection.append(" AND " + Contacts.HAS_PHONE_NUMBER + "=1");
243 }
244 break;
245 }
246 case ContactListFilter.FILTER_TYPE_ACCOUNT: {
247 // We use query parameters for account filter, so no selection to add here.
248 break;
249 }
Walter Jang08379b12016-06-14 15:35:28 -0700250 case ContactListFilter.FILTER_TYPE_GROUP_MEMBERS: {
Walter Jang08379b12016-06-14 15:35:28 -0700251 break;
252 }
Tingting Wang0ac73ba2016-07-05 22:33:01 -0700253 case ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS: {
Marcus Hagerott6caf23f2016-08-18 15:02:42 -0700254 if (filter.accountType != null) {
255 selection.append(ContactsContract.RawContacts.ACCOUNT_TYPE)
256 .append("=?");
257 selectionArgs.add(filter.accountType);
258 if (filter.accountName != null) {
259 selection.append(" AND ").append(ContactsContract.RawContacts.ACCOUNT_NAME)
260 .append(("=?"));
261 selectionArgs.add(filter.accountName);
262 }
263 } else {
264 selection.append(AccountWithDataSet.LOCAL_ACCOUNT_SELECTION);
265 }
Tingting Wang0ac73ba2016-07-05 22:33:01 -0700266 break;
267 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700268 }
269 loader.setSelection(selection.toString());
270 loader.setSelectionArgs(selectionArgs.toArray(new String[0]));
271 }
272
273 @Override
274 protected void bindView(View itemView, int partition, Cursor cursor, int position) {
Brian Attwella5ad5572014-09-15 11:18:03 -0700275 super.bindView(itemView, partition, cursor, position);
Chiao Cheng89437e82012-11-01 13:41:51 -0700276 final ContactListItemView view = (ContactListItemView)itemView;
277
278 view.setHighlightedPrefix(isSearchMode() ? getUpperCaseQueryString() : null);
279
280 if (isSelectionVisible()) {
281 view.setActivated(isSelectedContact(partition, cursor));
282 }
283
284 bindSectionHeaderAndDivider(view, position, cursor);
285
286 if (isQuickContactEnabled()) {
287 bindQuickContact(view, partition, cursor, ContactQuery.CONTACT_PHOTO_ID,
288 ContactQuery.CONTACT_PHOTO_URI, ContactQuery.CONTACT_ID,
Yorke Lee9df5e192014-02-12 14:58:25 -0800289 ContactQuery.CONTACT_LOOKUP_KEY, ContactQuery.CONTACT_DISPLAY_NAME);
Chiao Cheng89437e82012-11-01 13:41:51 -0700290 } else {
291 if (getDisplayPhotos()) {
292 bindPhoto(view, partition, cursor);
293 }
294 }
295
Brian Attwell2ea151c2014-09-03 19:53:26 -0700296 bindNameAndViewId(view, cursor);
Chiao Cheng89437e82012-11-01 13:41:51 -0700297 bindPresenceAndStatusMessage(view, cursor);
298
299 if (isSearchMode()) {
300 bindSearchSnippet(view, cursor);
301 } else {
302 view.setSnippet(null);
303 }
304 }
305
306 private boolean isCustomFilterForPhoneNumbersOnly() {
307 // TODO: this flag should not be stored in shared prefs. It needs to be in the db.
308 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
309 return prefs.getBoolean(ContactsPreferences.PREF_DISPLAY_ONLY_PHONES,
310 ContactsPreferences.PREF_DISPLAY_ONLY_PHONES_DEFAULT);
311 }
312}