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