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.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 
    113     public ContactListAdapter(Context context) {
    114         super(context, ContactQuery.CONTACT_ID);
    115 
    116         mUnknownNameText = context.getText(R.string.missing_name);
    117     }
    118 
    119     public void setPhotoPosition(ContactListItemView.PhotoPosition photoPosition) {
    120         mPhotoPosition = photoPosition;
    121     }
    122 
    123     public ContactListItemView.PhotoPosition getPhotoPosition() {
    124         return mPhotoPosition;
    125     }
    126 
    127     public CharSequence getUnknownNameText() {
    128         return mUnknownNameText;
    129     }
    130 
    131     public long getSelectedContactDirectoryId() {
    132         return mSelectedContactDirectoryId;
    133     }
    134 
    135     public String getSelectedContactLookupKey() {
    136         return mSelectedContactLookupKey;
    137     }
    138 
    139     public long getSelectedContactId() {
    140         return mSelectedContactId;
    141     }
    142 
    143     public void setSelectedContact(long selectedDirectoryId, String lookupKey, long contactId) {
    144         mSelectedContactDirectoryId = selectedDirectoryId;
    145         mSelectedContactLookupKey = lookupKey;
    146         mSelectedContactId = contactId;
    147     }
    148 
    149     protected static Uri buildSectionIndexerUri(Uri uri) {
    150         return uri.buildUpon()
    151                 .appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true").build();
    152     }
    153 
    154     @Override
    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      */
    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 
    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      */
    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 
    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      */
    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
    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 
    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 
    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 
    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 
    281     protected void bindPresenceAndStatusMessage(final ContactListItemView view, Cursor cursor) {
    282         view.showPresenceAndStatusMessage(cursor, ContactQuery.CONTACT_PRESENCE_STATUS,
    283                 ContactQuery.CONTACT_CONTACT_STATUS);
    284     }
    285 
    286     protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) {
    287         view.showSnippet(cursor, ContactQuery.CONTACT_SNIPPET);
    288     }
    289 
    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 
    344     public boolean hasValidSelection() {
    345         return getSelectedContactPosition() != -1;
    346     }
    347 
    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
    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      */
    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      */
    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      */
    439     private String[] replaceFirstString(String[] sourceProjection) {
    440         String[] result = sourceProjection.clone();
    441         result[0] = Data.CONTACT_ID;
    442         return result;
    443     }
    444 }
    445