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