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 */ 16 package com.android.contacts.common.list; 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; 28 import android.provider.ContactsContract.Directory; 29 import android.provider.ContactsContract.SearchSnippetColumns; 30 import android.text.TextUtils; 31 import android.view.View; 32 33 import com.android.contacts.common.preference.ContactsPreferences; 34 35 import java.util.ArrayList; 36 import java.util.List; 37 38 /** 39 * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type. 40 */ 41 public class DefaultContactListAdapter extends ContactListAdapter { 42 43 public static final char SNIPPET_START_MATCH = '\u0001'; 44 public static final char SNIPPET_END_MATCH = '\u0001'; 45 public static final String SNIPPET_ELLIPSIS = "\u2026"; 46 public static final int SNIPPET_MAX_TOKENS = 5; 47 48 public static final String SNIPPET_ARGS = SNIPPET_START_MATCH + "," + SNIPPET_END_MATCH + "," 49 + SNIPPET_ELLIPSIS + "," + SNIPPET_MAX_TOKENS; 50 51 public DefaultContactListAdapter(Context context) { 52 super(context); 53 } 54 55 @Override 56 public void configureLoader(CursorLoader loader, long directoryId) { 57 if (loader instanceof ProfileAndContactsLoader) { 58 ((ProfileAndContactsLoader) loader).setLoadProfile(shouldIncludeProfile()); 59 } 60 61 ContactListFilter filter = getFilter(); 62 if (isSearchMode()) { 63 String query = getQueryString(); 64 if (query == null) { 65 query = ""; 66 } 67 query = query.trim(); 68 if (TextUtils.isEmpty(query)) { 69 // Regardless of the directory, we don't want anything returned, 70 // so let's just send a "nothing" query to the local directory. 71 loader.setUri(Contacts.CONTENT_URI); 72 loader.setProjection(getProjection(false)); 73 loader.setSelection("0"); 74 } else { 75 Builder builder = Contacts.CONTENT_FILTER_URI.buildUpon(); 76 builder.appendPath(query); // Builder will encode the query 77 builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, 78 String.valueOf(directoryId)); 79 if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE) { 80 builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, 81 String.valueOf(getDirectoryResultLimit(getDirectoryById(directoryId)))); 82 } 83 builder.appendQueryParameter(SearchSnippetColumns.SNIPPET_ARGS_PARAM_KEY, 84 SNIPPET_ARGS); 85 builder.appendQueryParameter(SearchSnippetColumns.DEFERRED_SNIPPETING_KEY,"1"); 86 loader.setUri(builder.build()); 87 loader.setProjection(getProjection(true)); 88 } 89 } else { 90 configureUri(loader, directoryId, filter); 91 loader.setProjection(getProjection(false)); 92 configureSelection(loader, directoryId, filter); 93 } 94 95 String sortOrder; 96 if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) { 97 sortOrder = Contacts.SORT_KEY_PRIMARY; 98 } else { 99 sortOrder = Contacts.SORT_KEY_ALTERNATIVE; 100 } 101 102 loader.setSortOrder(sortOrder); 103 } 104 105 protected void configureUri(CursorLoader loader, long directoryId, ContactListFilter filter) { 106 Uri uri = Contacts.CONTENT_URI; 107 if (filter != null && filter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) { 108 String lookupKey = getSelectedContactLookupKey(); 109 if (lookupKey != null) { 110 uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey); 111 } else { 112 uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, getSelectedContactId()); 113 } 114 } 115 116 if (directoryId == Directory.DEFAULT && isSectionHeaderDisplayEnabled()) { 117 uri = ContactListAdapter.buildSectionIndexerUri(uri); 118 } 119 120 // The "All accounts" filter is the same as the entire contents of Directory.DEFAULT 121 if (filter != null 122 && filter.filterType != ContactListFilter.FILTER_TYPE_CUSTOM 123 && filter.filterType != ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) { 124 final Uri.Builder builder = uri.buildUpon(); 125 builder.appendQueryParameter( 126 ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT)); 127 if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) { 128 filter.addAccountQueryParameterToUrl(builder); 129 } 130 uri = builder.build(); 131 } 132 133 loader.setUri(uri); 134 } 135 136 private void configureSelection( 137 CursorLoader loader, long directoryId, ContactListFilter filter) { 138 if (filter == null) { 139 return; 140 } 141 142 if (directoryId != Directory.DEFAULT) { 143 return; 144 } 145 146 StringBuilder selection = new StringBuilder(); 147 List<String> selectionArgs = new ArrayList<String>(); 148 149 switch (filter.filterType) { 150 case ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS: { 151 // We have already added directory=0 to the URI, which takes care of this 152 // filter 153 break; 154 } 155 case ContactListFilter.FILTER_TYPE_SINGLE_CONTACT: { 156 // We have already added the lookup key to the URI, which takes care of this 157 // filter 158 break; 159 } 160 case ContactListFilter.FILTER_TYPE_STARRED: { 161 selection.append(Contacts.STARRED + "!=0"); 162 break; 163 } 164 case ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY: { 165 selection.append(Contacts.HAS_PHONE_NUMBER + "=1"); 166 break; 167 } 168 case ContactListFilter.FILTER_TYPE_CUSTOM: { 169 selection.append(Contacts.IN_VISIBLE_GROUP + "=1"); 170 if (isCustomFilterForPhoneNumbersOnly()) { 171 selection.append(" AND " + Contacts.HAS_PHONE_NUMBER + "=1"); 172 } 173 break; 174 } 175 case ContactListFilter.FILTER_TYPE_ACCOUNT: { 176 // We use query parameters for account filter, so no selection to add here. 177 break; 178 } 179 } 180 loader.setSelection(selection.toString()); 181 loader.setSelectionArgs(selectionArgs.toArray(new String[0])); 182 } 183 184 @Override 185 protected void bindView(View itemView, int partition, Cursor cursor, int position) { 186 final ContactListItemView view = (ContactListItemView)itemView; 187 188 view.setHighlightedPrefix(isSearchMode() ? getUpperCaseQueryString() : null); 189 190 if (isSelectionVisible()) { 191 view.setActivated(isSelectedContact(partition, cursor)); 192 } 193 194 bindSectionHeaderAndDivider(view, position, cursor); 195 196 if (isQuickContactEnabled()) { 197 bindQuickContact(view, partition, cursor, ContactQuery.CONTACT_PHOTO_ID, 198 ContactQuery.CONTACT_PHOTO_URI, ContactQuery.CONTACT_ID, 199 ContactQuery.CONTACT_LOOKUP_KEY, ContactQuery.CONTACT_DISPLAY_NAME); 200 } else { 201 if (getDisplayPhotos()) { 202 bindPhoto(view, partition, cursor); 203 } 204 } 205 206 bindName(view, cursor); 207 bindPresenceAndStatusMessage(view, cursor); 208 209 if (isSearchMode()) { 210 bindSearchSnippet(view, cursor); 211 } else { 212 view.setSnippet(null); 213 } 214 } 215 216 private boolean isCustomFilterForPhoneNumbersOnly() { 217 // TODO: this flag should not be stored in shared prefs. It needs to be in the db. 218 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); 219 return prefs.getBoolean(ContactsPreferences.PREF_DISPLAY_ONLY_PHONES, 220 ContactsPreferences.PREF_DISPLAY_ONLY_PHONES_DEFAULT); 221 } 222 } 223