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