blob: 1be48c4299938a004b9ac442e85493be42652299 [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.Context;
19import android.database.Cursor;
20import android.net.Uri;
21import android.provider.ContactsContract;
22import android.provider.ContactsContract.ContactCounts;
23import android.provider.ContactsContract.Contacts;
24import android.provider.ContactsContract.Directory;
25import android.provider.ContactsContract.SearchSnippetColumns;
26import android.text.TextUtils;
27import android.view.View;
28import android.view.ViewGroup;
29import android.widget.ListView;
30
31import com.android.contacts.common.R;
32
33/**
34 * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
35 * Also includes support for including the {@link ContactsContract.Profile} record in the
36 * list.
37 */
38public abstract class ContactListAdapter extends ContactEntryListAdapter {
39
40 protected static class ContactQuery {
41 private static final String[] CONTACT_PROJECTION_PRIMARY = new String[] {
42 Contacts._ID, // 0
43 Contacts.DISPLAY_NAME_PRIMARY, // 1
44 Contacts.CONTACT_PRESENCE, // 2
45 Contacts.CONTACT_STATUS, // 3
46 Contacts.PHOTO_ID, // 4
47 Contacts.PHOTO_THUMBNAIL_URI, // 5
48 Contacts.LOOKUP_KEY, // 6
49 Contacts.IS_USER_PROFILE, // 7
50 };
51
52 private static final String[] CONTACT_PROJECTION_ALTERNATIVE = new String[] {
53 Contacts._ID, // 0
54 Contacts.DISPLAY_NAME_ALTERNATIVE, // 1
55 Contacts.CONTACT_PRESENCE, // 2
56 Contacts.CONTACT_STATUS, // 3
57 Contacts.PHOTO_ID, // 4
58 Contacts.PHOTO_THUMBNAIL_URI, // 5
59 Contacts.LOOKUP_KEY, // 6
60 Contacts.IS_USER_PROFILE, // 7
61 };
62
63 private static final String[] FILTER_PROJECTION_PRIMARY = new String[] {
64 Contacts._ID, // 0
65 Contacts.DISPLAY_NAME_PRIMARY, // 1
66 Contacts.CONTACT_PRESENCE, // 2
67 Contacts.CONTACT_STATUS, // 3
68 Contacts.PHOTO_ID, // 4
69 Contacts.PHOTO_THUMBNAIL_URI, // 5
70 Contacts.LOOKUP_KEY, // 6
71 Contacts.IS_USER_PROFILE, // 7
72 SearchSnippetColumns.SNIPPET, // 8
73 };
74
75 private static final String[] FILTER_PROJECTION_ALTERNATIVE = new String[] {
76 Contacts._ID, // 0
77 Contacts.DISPLAY_NAME_ALTERNATIVE, // 1
78 Contacts.CONTACT_PRESENCE, // 2
79 Contacts.CONTACT_STATUS, // 3
80 Contacts.PHOTO_ID, // 4
81 Contacts.PHOTO_THUMBNAIL_URI, // 5
82 Contacts.LOOKUP_KEY, // 6
83 Contacts.IS_USER_PROFILE, // 7
84 SearchSnippetColumns.SNIPPET, // 8
85 };
86
87 public static final int CONTACT_ID = 0;
88 public static final int CONTACT_DISPLAY_NAME = 1;
89 public static final int CONTACT_PRESENCE_STATUS = 2;
90 public static final int CONTACT_CONTACT_STATUS = 3;
91 public static final int CONTACT_PHOTO_ID = 4;
92 public static final int CONTACT_PHOTO_URI = 5;
93 public static final int CONTACT_LOOKUP_KEY = 6;
94 public static final int CONTACT_IS_USER_PROFILE = 7;
95 public static final int CONTACT_SNIPPET = 8;
96 }
97
98 private CharSequence mUnknownNameText;
99
100 private long mSelectedContactDirectoryId;
101 private String mSelectedContactLookupKey;
102 private long mSelectedContactId;
103
104 public ContactListAdapter(Context context) {
105 super(context);
106
107 mUnknownNameText = context.getText(R.string.missing_name);
108 }
109
110 public CharSequence getUnknownNameText() {
111 return mUnknownNameText;
112 }
113
114 public long getSelectedContactDirectoryId() {
115 return mSelectedContactDirectoryId;
116 }
117
118 public String getSelectedContactLookupKey() {
119 return mSelectedContactLookupKey;
120 }
121
122 public long getSelectedContactId() {
123 return mSelectedContactId;
124 }
125
126 public void setSelectedContact(long selectedDirectoryId, String lookupKey, long contactId) {
127 mSelectedContactDirectoryId = selectedDirectoryId;
128 mSelectedContactLookupKey = lookupKey;
129 mSelectedContactId = contactId;
130 }
131
132 protected static Uri buildSectionIndexerUri(Uri uri) {
133 return uri.buildUpon()
134 .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
135 }
136
137 @Override
138 public String getContactDisplayName(int position) {
139 return ((Cursor) getItem(position)).getString(ContactQuery.CONTACT_DISPLAY_NAME);
140 }
141
142 /**
143 * Builds the {@link Contacts#CONTENT_LOOKUP_URI} for the given
144 * {@link ListView} position.
145 */
146 public Uri getContactUri(int position) {
147 int partitionIndex = getPartitionForPosition(position);
148 Cursor item = (Cursor)getItem(position);
149 return item != null ? getContactUri(partitionIndex, item) : null;
150 }
151
152 public Uri getContactUri(int partitionIndex, Cursor cursor) {
153 long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
154 String lookupKey = cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY);
155 Uri uri = Contacts.getLookupUri(contactId, lookupKey);
156 long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
157 if (directoryId != Directory.DEFAULT) {
158 uri = uri.buildUpon().appendQueryParameter(
159 ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
160 }
161 return uri;
162 }
163
164 /**
165 * Returns true if the specified contact is selected in the list. For a
166 * contact to be shown as selected, we need both the directory and and the
167 * lookup key to be the same. We are paying no attention to the contactId,
168 * because it is volatile, especially in the case of directories.
169 */
170 public boolean isSelectedContact(int partitionIndex, Cursor cursor) {
171 long directoryId = ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
172 if (getSelectedContactDirectoryId() != directoryId) {
173 return false;
174 }
175 String lookupKey = getSelectedContactLookupKey();
176 if (lookupKey != null && TextUtils.equals(lookupKey,
177 cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY))) {
178 return true;
179 }
180
181 return directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE
182 && getSelectedContactId() == cursor.getLong(ContactQuery.CONTACT_ID);
183 }
184
185 @Override
186 protected View newView(Context context, int partition, Cursor cursor, int position,
187 ViewGroup parent) {
188 ContactListItemView view = new ContactListItemView(context, null);
189 view.setUnknownNameText(mUnknownNameText);
190 view.setQuickContactEnabled(isQuickContactEnabled());
191 view.setActivatedStateSupported(isSelectionVisible());
192 return view;
193 }
194
195 protected void bindSectionHeaderAndDivider(ContactListItemView view, int position,
196 Cursor cursor) {
197 if (isSectionHeaderDisplayEnabled()) {
198 Placement placement = getItemPlacementInSection(position);
199
200 // First position, set the contacts number string
201 if (position == 0 && cursor.getInt(ContactQuery.CONTACT_IS_USER_PROFILE) == 1) {
202 view.setCountView(getContactsCount());
203 } else {
204 view.setCountView(null);
205 }
206 view.setSectionHeader(placement.sectionHeader);
207 view.setDividerVisible(!placement.lastInSection);
208 } else {
209 view.setSectionHeader(null);
210 view.setDividerVisible(true);
211 view.setCountView(null);
212 }
213 }
214
215 protected void bindPhoto(final ContactListItemView view, int partitionIndex, Cursor cursor) {
216 if (!isPhotoSupported(partitionIndex)) {
217 view.removePhotoView();
218 return;
219 }
220
221 // Set the photo, if available
222 long photoId = 0;
223 if (!cursor.isNull(ContactQuery.CONTACT_PHOTO_ID)) {
224 photoId = cursor.getLong(ContactQuery.CONTACT_PHOTO_ID);
225 }
226
227 if (photoId != 0) {
228 getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false);
229 } else {
230 final String photoUriString = cursor.getString(ContactQuery.CONTACT_PHOTO_URI);
231 final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
232 getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false);
233 }
234 }
235
236 protected void bindName(final ContactListItemView view, Cursor cursor) {
237 view.showDisplayName(
238 cursor, ContactQuery.CONTACT_DISPLAY_NAME, getContactNameDisplayOrder());
239 // Note: we don't show phonetic any more (See issue 5265330)
240 }
241
242 protected void bindPresenceAndStatusMessage(final ContactListItemView view, Cursor cursor) {
243 view.showPresenceAndStatusMessage(cursor, ContactQuery.CONTACT_PRESENCE_STATUS,
244 ContactQuery.CONTACT_CONTACT_STATUS);
245 }
246
247 protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) {
248 view.showSnippet(cursor, ContactQuery.CONTACT_SNIPPET);
249 }
250
251 public int getSelectedContactPosition() {
252 if (mSelectedContactLookupKey == null && mSelectedContactId == 0) {
253 return -1;
254 }
255
256 Cursor cursor = null;
257 int partitionIndex = -1;
258 int partitionCount = getPartitionCount();
259 for (int i = 0; i < partitionCount; i++) {
260 DirectoryPartition partition = (DirectoryPartition) getPartition(i);
261 if (partition.getDirectoryId() == mSelectedContactDirectoryId) {
262 partitionIndex = i;
263 break;
264 }
265 }
266 if (partitionIndex == -1) {
267 return -1;
268 }
269
270 cursor = getCursor(partitionIndex);
271 if (cursor == null) {
272 return -1;
273 }
274
275 cursor.moveToPosition(-1); // Reset cursor
276 int offset = -1;
277 while (cursor.moveToNext()) {
278 if (mSelectedContactLookupKey != null) {
279 String lookupKey = cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY);
280 if (mSelectedContactLookupKey.equals(lookupKey)) {
281 offset = cursor.getPosition();
282 break;
283 }
284 }
285 if (mSelectedContactId != 0 && (mSelectedContactDirectoryId == Directory.DEFAULT
286 || mSelectedContactDirectoryId == Directory.LOCAL_INVISIBLE)) {
287 long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
288 if (contactId == mSelectedContactId) {
289 offset = cursor.getPosition();
290 break;
291 }
292 }
293 }
294 if (offset == -1) {
295 return -1;
296 }
297
298 int position = getPositionForPartition(partitionIndex) + offset;
299 if (hasHeader(partitionIndex)) {
300 position++;
301 }
302 return position;
303 }
304
305 public boolean hasValidSelection() {
306 return getSelectedContactPosition() != -1;
307 }
308
309 public Uri getFirstContactUri() {
310 int partitionCount = getPartitionCount();
311 for (int i = 0; i < partitionCount; i++) {
312 DirectoryPartition partition = (DirectoryPartition) getPartition(i);
313 if (partition.isLoading()) {
314 continue;
315 }
316
317 Cursor cursor = getCursor(i);
318 if (cursor == null) {
319 continue;
320 }
321
322 if (!cursor.moveToFirst()) {
323 continue;
324 }
325
326 return getContactUri(i, cursor);
327 }
328
329 return null;
330 }
331
332 @Override
333 public void changeCursor(int partitionIndex, Cursor cursor) {
334 super.changeCursor(partitionIndex, cursor);
335
336 // Check if a profile exists
337 if (cursor != null && cursor.getCount() > 0) {
338 cursor.moveToFirst();
339 setProfileExists(cursor.getInt(ContactQuery.CONTACT_IS_USER_PROFILE) == 1);
340 }
341 }
342
343 /**
344 * @return Projection useful for children.
345 */
346 protected final String[] getProjection(boolean forSearch) {
347 final int sortOrder = getContactNameDisplayOrder();
348 if (forSearch) {
349 if (sortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
350 return ContactQuery.FILTER_PROJECTION_PRIMARY;
351 } else {
352 return ContactQuery.FILTER_PROJECTION_ALTERNATIVE;
353 }
354 } else {
355 if (sortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
356 return ContactQuery.CONTACT_PROJECTION_PRIMARY;
357 } else {
358 return ContactQuery.CONTACT_PROJECTION_ALTERNATIVE;
359 }
360 }
361 }
362}