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