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.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.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}
363