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  */
16 package com.android.contacts.common.list;
17 
18 import android.content.Context;
19 import android.database.Cursor;
20 import android.net.Uri;
21 import android.provider.ContactsContract;
22 import android.provider.ContactsContract.Contacts;
23 import android.provider.ContactsContract.Directory;
24 import android.provider.ContactsContract.SearchSnippets;
25 import android.text.TextUtils;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.widget.ListView;
29 
30 import com.android.contacts.common.ContactPhotoManager;
31 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
32 import com.android.contacts.common.R;
33 import 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  */
40 public 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 
ContactListAdapter(Context context)107     public ContactListAdapter(Context context) {
108         super(context);
109 
110         mUnknownNameText = context.getText(R.string.missing_name);
111     }
112 
setPhotoPosition(ContactListItemView.PhotoPosition photoPosition)113     public void setPhotoPosition(ContactListItemView.PhotoPosition photoPosition) {
114         mPhotoPosition = photoPosition;
115     }
116 
getPhotoPosition()117     public ContactListItemView.PhotoPosition getPhotoPosition() {
118         return mPhotoPosition;
119     }
120 
getUnknownNameText()121     public CharSequence getUnknownNameText() {
122         return mUnknownNameText;
123     }
124 
getSelectedContactDirectoryId()125     public long getSelectedContactDirectoryId() {
126         return mSelectedContactDirectoryId;
127     }
128 
getSelectedContactLookupKey()129     public String getSelectedContactLookupKey() {
130         return mSelectedContactLookupKey;
131     }
132 
getSelectedContactId()133     public long getSelectedContactId() {
134         return mSelectedContactId;
135     }
136 
setSelectedContact(long selectedDirectoryId, String lookupKey, long contactId)137     public void setSelectedContact(long selectedDirectoryId, String lookupKey, long contactId) {
138         mSelectedContactDirectoryId = selectedDirectoryId;
139         mSelectedContactLookupKey = lookupKey;
140         mSelectedContactId = contactId;
141     }
142 
buildSectionIndexerUri(Uri uri)143     protected static Uri buildSectionIndexerUri(Uri uri) {
144         return uri.buildUpon()
145                 .appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true").build();
146     }
147 
148     @Override
getContactDisplayName(int position)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      */
getContactUri(int position)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 
getContactUri(int partitionIndex, Cursor cursor)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      */
isSelectedContact(int partitionIndex, Cursor cursor)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
newView( Context context, int partition, Cursor cursor, int position, ViewGroup parent)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 
bindSectionHeaderAndDivider(ContactListItemView view, int position, Cursor cursor)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 
bindPhoto(final ContactListItemView view, int partitionIndex, Cursor cursor)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 
bindNameAndViewId(final ContactListItemView view, Cursor cursor)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 
bindPresenceAndStatusMessage(final ContactListItemView view, Cursor cursor)258     protected void bindPresenceAndStatusMessage(final ContactListItemView view, Cursor cursor) {
259         view.showPresenceAndStatusMessage(cursor, ContactQuery.CONTACT_PRESENCE_STATUS,
260                 ContactQuery.CONTACT_CONTACT_STATUS);
261     }
262 
bindSearchSnippet(final ContactListItemView view, Cursor cursor)263     protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) {
264         view.showSnippet(cursor, ContactQuery.CONTACT_SNIPPET);
265     }
266 
getSelectedContactPosition()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 
hasValidSelection()321     public boolean hasValidSelection() {
322         return getSelectedContactPosition() != -1;
323     }
324 
getFirstContactUri()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
changeCursor(int partitionIndex, Cursor cursor)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.moveToFirst()) {
354             setProfileExists(cursor.getInt(ContactQuery.CONTACT_IS_USER_PROFILE) == 1);
355         }
356     }
357 
358     /**
359      * @return Projection useful for children.
360      */
getProjection(boolean forSearch)361     protected final String[] getProjection(boolean forSearch) {
362         final int sortOrder = getContactNameDisplayOrder();
363         if (forSearch) {
364             if (sortOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
365                 return ContactQuery.FILTER_PROJECTION_PRIMARY;
366             } else {
367                 return ContactQuery.FILTER_PROJECTION_ALTERNATIVE;
368             }
369         } else {
370             if (sortOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
371                 return ContactQuery.CONTACT_PROJECTION_PRIMARY;
372             } else {
373                 return ContactQuery.CONTACT_PROJECTION_ALTERNATIVE;
374             }
375         }
376     }
377 }
378