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