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.ContentUris; |
| 19 | import android.content.Context; |
| 20 | import android.content.CursorLoader; |
| 21 | import android.content.SharedPreferences; |
| 22 | import android.database.Cursor; |
| 23 | import android.net.Uri; |
| 24 | import android.net.Uri.Builder; |
| 25 | import android.preference.PreferenceManager; |
| 26 | import android.provider.ContactsContract; |
| 27 | import android.provider.ContactsContract.Contacts; |
Tingting Wang | 0ac73ba | 2016-07-05 22:33:01 -0700 | [diff] [blame] | 28 | import android.provider.ContactsContract.Data; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 29 | import android.provider.ContactsContract.Directory; |
Yorke Lee | a12a04c | 2014-07-01 10:39:42 -0700 | [diff] [blame] | 30 | import android.provider.ContactsContract.SearchSnippets; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 31 | import android.text.TextUtils; |
| 32 | import android.view.View; |
| 33 | |
Walter Jang | f61f0ee | 2016-11-29 18:40:17 +0000 | [diff] [blame] | 34 | import com.android.contacts.Experiments; |
Gary Mai | 69c182a | 2016-12-05 13:07:03 -0800 | [diff] [blame] | 35 | import com.android.contacts.compat.ContactsCompat; |
| 36 | import com.android.contacts.model.account.AccountWithDataSet; |
| 37 | import com.android.contacts.preference.ContactsPreferences; |
Walter Jang | f61f0ee | 2016-11-29 18:40:17 +0000 | [diff] [blame] | 38 | import com.android.contactsbind.experiments.Flags; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 39 | |
| 40 | import java.util.ArrayList; |
| 41 | import java.util.List; |
| 42 | |
| 43 | /** |
| 44 | * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type. |
| 45 | */ |
| 46 | public class DefaultContactListAdapter extends ContactListAdapter { |
| 47 | |
Yorke Lee | aed0a9e | 2014-07-08 08:03:56 -0700 | [diff] [blame] | 48 | public static final char SNIPPET_START_MATCH = '['; |
| 49 | public static final char SNIPPET_END_MATCH = ']'; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 50 | |
Walter Jang | 5a8f91c | 2016-03-30 11:21:55 -0700 | [diff] [blame] | 51 | // 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 Jang | 6474383 | 2016-03-17 21:01:07 -0700 | [diff] [blame] | 78 | |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 79 | public DefaultContactListAdapter(Context context) { |
| 80 | super(context); |
| 81 | } |
| 82 | |
| 83 | @Override |
| 84 | public void configureLoader(CursorLoader loader, long directoryId) { |
Wenyi Wang | be88bed | 2016-05-13 12:04:14 -0700 | [diff] [blame] | 85 | if (loader instanceof FavoritesAndContactsLoader) { |
| 86 | ((FavoritesAndContactsLoader) loader).setLoadFavorites(shouldIncludeFavorites()); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 87 | } |
| 88 | |
Walter Jang | 1f82861 | 2016-02-09 16:15:37 -0800 | [diff] [blame] | 89 | String sortOrder = null; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 90 | if (isSearchMode()) { |
| 91 | String query = getQueryString(); |
Walter Jang | 1f82861 | 2016-02-09 16:15:37 -0800 | [diff] [blame] | 92 | if (query == null) query = ""; |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 93 | 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 Jang | 08379b1 | 2016-06-14 15:35:28 -0700 | [diff] [blame] | 100 | } else if (isGroupMembersFilter()) { |
| 101 | final ContactListFilter filter = getFilter(); |
| 102 | configureUri(loader, directoryId, filter); |
Walter Jang | 1842e14 | 2016-03-31 09:03:18 -0700 | [diff] [blame] | 103 | // 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 Jang | 08379b1 | 2016-06-14 15:35:28 -0700 | [diff] [blame] | 106 | 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 113 | } else { |
Ricky Wai | dffb27d | 2015-12-08 16:58:27 +0000 | [diff] [blame] | 114 | final Builder builder = ContactsCompat.getContentUri().buildUpon(); |
Walter Jang | 5456440 | 2016-01-18 11:56:19 -0800 | [diff] [blame] | 115 | appendSearchParameters(builder, query, directoryId); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 116 | loader.setUri(builder.build()); |
| 117 | loader.setProjection(getProjection(true)); |
Walter Jang | c462653 | 2016-06-21 14:06:51 -0700 | [diff] [blame] | 118 | sortOrder = STREQUENT_SORT; |
Walter Jang | f61f0ee | 2016-11-29 18:40:17 +0000 | [diff] [blame] | 119 | 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 126 | } |
| 127 | } else { |
Walter Jang | 1f82861 | 2016-02-09 16:15:37 -0800 | [diff] [blame] | 128 | final ContactListFilter filter = getFilter(); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 129 | configureUri(loader, directoryId, filter); |
Tingting Wang | 0ac73ba | 2016-07-05 22:33:01 -0700 | [diff] [blame] | 130 | 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 136 | configureSelection(loader, directoryId, filter); |
| 137 | } |
| 138 | |
Yorke Lee | b3d841a | 2014-07-10 11:38:55 -0700 | [diff] [blame] | 139 | if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) { |
Walter Jang | 1f82861 | 2016-02-09 16:15:37 -0800 | [diff] [blame] | 140 | if (sortOrder == null) { |
| 141 | sortOrder = Contacts.SORT_KEY_PRIMARY; |
| 142 | } else { |
| 143 | sortOrder += ", " + Contacts.SORT_KEY_PRIMARY; |
| 144 | } |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 145 | } else { |
Walter Jang | 1f82861 | 2016-02-09 16:15:37 -0800 | [diff] [blame] | 146 | if (sortOrder == null) { |
| 147 | sortOrder = Contacts.SORT_KEY_ALTERNATIVE; |
| 148 | } else { |
| 149 | sortOrder += ", " + Contacts.SORT_KEY_ALTERNATIVE; |
| 150 | } |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 151 | } |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 152 | loader.setSortOrder(sortOrder); |
| 153 | } |
| 154 | |
Walter Jang | 08379b1 | 2016-06-14 15:35:28 -0700 | [diff] [blame] | 155 | private boolean isGroupMembersFilter() { |
| 156 | final ContactListFilter filter = getFilter(); |
| 157 | return filter != null && filter.filterType == ContactListFilter.FILTER_TYPE_GROUP_MEMBERS; |
| 158 | } |
| 159 | |
Walter Jang | 5456440 | 2016-01-18 11:56:19 -0800 | [diff] [blame] | 160 | 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 Jang | 8f01264 | 2016-02-12 17:43:27 -0800 | [diff] [blame] | 168 | builder.appendQueryParameter(SearchSnippets.DEFERRED_SNIPPETING_KEY, "1"); |
Walter Jang | 5456440 | 2016-01-18 11:56:19 -0800 | [diff] [blame] | 169 | } |
| 170 | |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 171 | protected void configureUri(CursorLoader loader, long directoryId, ContactListFilter filter) { |
| 172 | Uri uri = Contacts.CONTENT_URI; |
Tingting Wang | 0ac73ba | 2016-07-05 22:33:01 -0700 | [diff] [blame] | 173 | 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 Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 183 | } |
| 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 Jang | 08379b1 | 2016-06-14 15:35:28 -0700 | [diff] [blame] | 197 | if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT |
| 198 | || filter.filterType == ContactListFilter.FILTER_TYPE_GROUP_MEMBERS) { |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 199 | 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 Jang | 08379b1 | 2016-06-14 15:35:28 -0700 | [diff] [blame] | 250 | case ContactListFilter.FILTER_TYPE_GROUP_MEMBERS: { |
Walter Jang | 08379b1 | 2016-06-14 15:35:28 -0700 | [diff] [blame] | 251 | break; |
| 252 | } |
Tingting Wang | 0ac73ba | 2016-07-05 22:33:01 -0700 | [diff] [blame] | 253 | case ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS: { |
Marcus Hagerott | 6caf23f | 2016-08-18 15:02:42 -0700 | [diff] [blame] | 254 | 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 Wang | 0ac73ba | 2016-07-05 22:33:01 -0700 | [diff] [blame] | 266 | break; |
| 267 | } |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 268 | } |
| 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 Attwell | a5ad557 | 2014-09-15 11:18:03 -0700 | [diff] [blame] | 275 | super.bindView(itemView, partition, cursor, position); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 276 | 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 Lee | 9df5e19 | 2014-02-12 14:58:25 -0800 | [diff] [blame] | 289 | ContactQuery.CONTACT_LOOKUP_KEY, ContactQuery.CONTACT_DISPLAY_NAME); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 290 | } else { |
| 291 | if (getDisplayPhotos()) { |
| 292 | bindPhoto(view, partition, cursor); |
| 293 | } |
| 294 | } |
| 295 | |
Brian Attwell | 2ea151c | 2014-09-03 19:53:26 -0700 | [diff] [blame] | 296 | bindNameAndViewId(view, cursor); |
Chiao Cheng | 89437e8 | 2012-11-01 13:41:51 -0700 | [diff] [blame] | 297 | 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 | } |