ContactListAdapter.java revision 3f9c2f426058413055fa54c08c69ad9461717658
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.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.ContactPhotoManager;
32import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
33import com.android.contacts.common.R;
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            SearchSnippetColumns.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            SearchSnippetColumns.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(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "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.setActivatedStateSupported(isSelectionVisible());
203        if (mPhotoPosition != null) {
204            view.setPhotoPosition(mPhotoPosition);
205        }
206        return view;
207    }
208
209    protected void bindSectionHeaderAndDivider(ContactListItemView view, int position,
210            Cursor cursor) {
211        if (isSectionHeaderDisplayEnabled()) {
212            Placement placement = getItemPlacementInSection(position);
213            view.setSectionHeader(placement.sectionHeader);
214        } else {
215            view.setSectionHeader(null);
216        }
217    }
218
219    protected void bindPhoto(final ContactListItemView view, int partitionIndex, Cursor cursor) {
220        if (!isPhotoSupported(partitionIndex)) {
221            view.removePhotoView();
222            return;
223        }
224
225        // Set the photo, if available
226        long photoId = 0;
227        if (!cursor.isNull(ContactQuery.CONTACT_PHOTO_ID)) {
228            photoId = cursor.getLong(ContactQuery.CONTACT_PHOTO_ID);
229        }
230
231        if (photoId != 0) {
232            getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false,
233                    getCircularPhotos(), null);
234        } else {
235            final String photoUriString = cursor.getString(ContactQuery.CONTACT_PHOTO_URI);
236            final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
237            DefaultImageRequest request = null;
238            if (photoUri == null) {
239                request = getDefaultImageRequestFromCursor(cursor,
240                        ContactQuery.CONTACT_DISPLAY_NAME,
241                        ContactQuery.CONTACT_LOOKUP_KEY);
242            }
243            getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false,
244                    getCircularPhotos(), request);
245        }
246    }
247
248    protected void bindName(final ContactListItemView view, Cursor cursor) {
249        view.showDisplayName(
250                cursor, ContactQuery.CONTACT_DISPLAY_NAME, getContactNameDisplayOrder());
251        // Note: we don't show phonetic any more (See issue 5265330)
252    }
253
254    protected void bindPresenceAndStatusMessage(final ContactListItemView view, Cursor cursor) {
255        view.showPresenceAndStatusMessage(cursor, ContactQuery.CONTACT_PRESENCE_STATUS,
256                ContactQuery.CONTACT_CONTACT_STATUS);
257    }
258
259    protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) {
260        view.showSnippet(cursor, ContactQuery.CONTACT_SNIPPET);
261    }
262
263    public int getSelectedContactPosition() {
264        if (mSelectedContactLookupKey == null && mSelectedContactId == 0) {
265            return -1;
266        }
267
268        Cursor cursor = null;
269        int partitionIndex = -1;
270        int partitionCount = getPartitionCount();
271        for (int i = 0; i < partitionCount; i++) {
272            DirectoryPartition partition = (DirectoryPartition) getPartition(i);
273            if (partition.getDirectoryId() == mSelectedContactDirectoryId) {
274                partitionIndex = i;
275                break;
276            }
277        }
278        if (partitionIndex == -1) {
279            return -1;
280        }
281
282        cursor = getCursor(partitionIndex);
283        if (cursor == null) {
284            return -1;
285        }
286
287        cursor.moveToPosition(-1);      // Reset cursor
288        int offset = -1;
289        while (cursor.moveToNext()) {
290            if (mSelectedContactLookupKey != null) {
291                String lookupKey = cursor.getString(ContactQuery.CONTACT_LOOKUP_KEY);
292                if (mSelectedContactLookupKey.equals(lookupKey)) {
293                    offset = cursor.getPosition();
294                    break;
295                }
296            }
297            if (mSelectedContactId != 0 && (mSelectedContactDirectoryId == Directory.DEFAULT
298                    || mSelectedContactDirectoryId == Directory.LOCAL_INVISIBLE)) {
299                long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
300                if (contactId == mSelectedContactId) {
301                    offset = cursor.getPosition();
302                    break;
303                }
304            }
305        }
306        if (offset == -1) {
307            return -1;
308        }
309
310        int position = getPositionForPartition(partitionIndex) + offset;
311        if (hasHeader(partitionIndex)) {
312            position++;
313        }
314        return position;
315    }
316
317    public boolean hasValidSelection() {
318        return getSelectedContactPosition() != -1;
319    }
320
321    public Uri getFirstContactUri() {
322        int partitionCount = getPartitionCount();
323        for (int i = 0; i < partitionCount; i++) {
324            DirectoryPartition partition = (DirectoryPartition) getPartition(i);
325            if (partition.isLoading()) {
326                continue;
327            }
328
329            Cursor cursor = getCursor(i);
330            if (cursor == null) {
331                continue;
332            }
333
334            if (!cursor.moveToFirst()) {
335                continue;
336            }
337
338            return getContactUri(i, cursor);
339        }
340
341        return null;
342    }
343
344    @Override
345    public void changeCursor(int partitionIndex, Cursor cursor) {
346        super.changeCursor(partitionIndex, cursor);
347
348        // Check if a profile exists
349        if (cursor != null && cursor.getCount() > 0) {
350            cursor.moveToFirst();
351            setProfileExists(cursor.getInt(ContactQuery.CONTACT_IS_USER_PROFILE) == 1);
352        }
353    }
354
355    /**
356     * @return Projection useful for children.
357     */
358    protected final String[] getProjection(boolean forSearch) {
359        final int sortOrder = getContactNameDisplayOrder();
360        if (forSearch) {
361            if (sortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
362                return ContactQuery.FILTER_PROJECTION_PRIMARY;
363            } else {
364                return ContactQuery.FILTER_PROJECTION_ALTERNATIVE;
365            }
366        } else {
367            if (sortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
368                return ContactQuery.CONTACT_PROJECTION_PRIMARY;
369            } else {
370                return ContactQuery.CONTACT_PROJECTION_ALTERNATIVE;
371            }
372        }
373    }
374}
375