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.ContentUris;
     19 import android.content.Context;
     20 import android.content.CursorLoader;
     21 import android.content.SharedPreferences;
     22 import android.database.Cursor;
     23 import android.net.Uri;
     24 import android.net.Uri.Builder;
     25 import android.preference.PreferenceManager;
     26 import android.provider.ContactsContract;
     27 import android.provider.ContactsContract.Contacts;
     28 import android.provider.ContactsContract.Data;
     29 import android.provider.ContactsContract.Directory;
     30 import android.provider.ContactsContract.SearchSnippets;
     31 import android.text.TextUtils;
     32 import android.view.View;
     33 
     34 import com.android.contacts.compat.ContactsCompat;
     35 import com.android.contacts.model.account.AccountWithDataSet;
     36 import com.android.contacts.preference.ContactsPreferences;
     37 
     38 import java.util.ArrayList;
     39 import java.util.List;
     40 
     41 /**
     42  * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
     43  */
     44 public class DefaultContactListAdapter extends ContactListAdapter {
     45 
     46     public static final char SNIPPET_START_MATCH = '[';
     47     public static final char SNIPPET_END_MATCH = ']';
     48 
     49     // Contacts contacted within the last 3 days (in seconds)
     50     private static final long LAST_TIME_USED_3_DAYS_SEC = 3L * 24 * 60 * 60;
     51 
     52     // Contacts contacted within the last 7 days (in seconds)
     53     private static final long LAST_TIME_USED_7_DAYS_SEC = 7L * 24 * 60 * 60;
     54 
     55     // Contacts contacted within the last 14 days (in seconds)
     56     private static final long LAST_TIME_USED_14_DAYS_SEC = 14L * 24 * 60 * 60;
     57 
     58     // Contacts contacted within the last 30 days (in seconds)
     59     private static final long LAST_TIME_USED_30_DAYS_SEC = 30L * 24 * 60 * 60;
     60 
     61     private static final String TIME_SINCE_LAST_USED_SEC =
     62             "(strftime('%s', 'now') - " + Contacts.LAST_TIME_CONTACTED + "/1000)";
     63 
     64     private static final String STREQUENT_SORT =
     65             "(CASE WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_3_DAYS_SEC +
     66                     " THEN 0 " +
     67                     " WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_7_DAYS_SEC +
     68                     " THEN 1 " +
     69                     " WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_14_DAYS_SEC +
     70                     " THEN 2 " +
     71                     " WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_30_DAYS_SEC +
     72                     " THEN 3 " +
     73                     " ELSE 4 END), " +
     74                     Contacts.TIMES_CONTACTED + " DESC, " +
     75                     Contacts.STARRED + " DESC";
     76 
     77     public DefaultContactListAdapter(Context context) {
     78         super(context);
     79     }
     80 
     81     @Override
     82     public void configureLoader(CursorLoader loader, long directoryId) {
     83         if (loader instanceof FavoritesAndContactsLoader) {
     84             ((FavoritesAndContactsLoader) loader).setLoadFavorites(shouldIncludeFavorites());
     85         }
     86 
     87         String sortOrder = null;
     88         if (isSearchMode()) {
     89             String query = getQueryString();
     90             if (query == null) query = "";
     91             query = query.trim();
     92             if (TextUtils.isEmpty(query)) {
     93                 // Regardless of the directory, we don't want anything returned,
     94                 // so let's just send a "nothing" query to the local directory.
     95                 loader.setUri(Contacts.CONTENT_URI);
     96                 loader.setProjection(getProjection(false));
     97                 loader.setSelection("0");
     98             } else if (isGroupMembersFilter()) {
     99                 final ContactListFilter filter = getFilter();
    100                 configureUri(loader, directoryId, filter);
    101                 // TODO: This is not the normal type to filter URI so we load the non-search
    102                 // projection. Consider creating a specific group member adapter to make it more
    103                 // clear.
    104                 loader.setProjection(getProjection(/* forSearch */ false));
    105                 loader.setSelection(
    106                         Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?1 OR " +
    107                         Contacts.DISPLAY_NAME_ALTERNATIVE + " LIKE ?1");
    108                 final String[] args = new String[1];
    109                 args[0] = query + "%";
    110                 loader.setSelectionArgs(args);
    111             } else {
    112                 final Builder builder = ContactsCompat.getContentUri().buildUpon();
    113                 appendSearchParameters(builder, query, directoryId);
    114                 loader.setUri(builder.build());
    115                 loader.setProjection(getProjection(true));
    116                 sortOrder = STREQUENT_SORT;
    117             }
    118         } else {
    119             final ContactListFilter filter = getFilter();
    120             configureUri(loader, directoryId, filter);
    121             if (filter != null
    122                     && filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
    123                 loader.setProjection(getDataProjectionForContacts(false));
    124             } else {
    125                 loader.setProjection(getProjection(false));
    126             }
    127             configureSelection(loader, directoryId, filter);
    128         }
    129 
    130         if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
    131             if (sortOrder == null) {
    132                 sortOrder = Contacts.SORT_KEY_PRIMARY;
    133             } else {
    134                 sortOrder += ", " + Contacts.SORT_KEY_PRIMARY;
    135             }
    136         } else {
    137             if (sortOrder == null) {
    138                 sortOrder = Contacts.SORT_KEY_ALTERNATIVE;
    139             } else {
    140                 sortOrder += ", " + Contacts.SORT_KEY_ALTERNATIVE;
    141             }
    142         }
    143         loader.setSortOrder(sortOrder);
    144     }
    145 
    146     private boolean isGroupMembersFilter() {
    147         final ContactListFilter filter = getFilter();
    148         return filter != null && filter.filterType == ContactListFilter.FILTER_TYPE_GROUP_MEMBERS;
    149     }
    150 
    151     private void appendSearchParameters(Builder builder, String query, long directoryId) {
    152         builder.appendPath(query); // Builder will encode the query
    153         builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
    154                 String.valueOf(directoryId));
    155         if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE) {
    156             builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
    157                     String.valueOf(getDirectoryResultLimit(getDirectoryById(directoryId))));
    158         }
    159         builder.appendQueryParameter(SearchSnippets.DEFERRED_SNIPPETING_KEY, "1");
    160     }
    161 
    162     protected void configureUri(CursorLoader loader, long directoryId, ContactListFilter filter) {
    163         Uri uri = Contacts.CONTENT_URI;
    164         if (filter != null) {
    165             if (filter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
    166                 String lookupKey = getSelectedContactLookupKey();
    167                 if (lookupKey != null) {
    168                     uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
    169                 } else {
    170                     uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, getSelectedContactId());
    171                 }
    172             } else if (filter.filterType == ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
    173                 uri = Data.CONTENT_URI;
    174             }
    175         }
    176 
    177         if (directoryId == Directory.DEFAULT && isSectionHeaderDisplayEnabled()) {
    178             uri = ContactListAdapter.buildSectionIndexerUri(uri);
    179         }
    180 
    181         if (filter != null
    182                 && filter.filterType != ContactListFilter.FILTER_TYPE_CUSTOM
    183                 && filter.filterType != ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
    184             final Uri.Builder builder = uri.buildUpon();
    185             if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
    186                 || filter.filterType == ContactListFilter.FILTER_TYPE_GROUP_MEMBERS) {
    187                 filter.addAccountQueryParameterToUrl(builder);
    188             }
    189             uri = builder.build();
    190         }
    191 
    192         loader.setUri(uri);
    193     }
    194 
    195     private void configureSelection(
    196             CursorLoader loader, long directoryId, ContactListFilter filter) {
    197         if (filter == null) {
    198             return;
    199         }
    200 
    201         if (directoryId != Directory.DEFAULT) {
    202             return;
    203         }
    204 
    205         StringBuilder selection = new StringBuilder();
    206         List<String> selectionArgs = new ArrayList<String>();
    207 
    208         switch (filter.filterType) {
    209             case ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS: {
    210                 // We have already added directory=0 to the URI, which takes care of this
    211                 // filter
    212                 break;
    213             }
    214             case ContactListFilter.FILTER_TYPE_SINGLE_CONTACT: {
    215                 // We have already added the lookup key to the URI, which takes care of this
    216                 // filter
    217                 break;
    218             }
    219             case ContactListFilter.FILTER_TYPE_STARRED: {
    220                 selection.append(Contacts.STARRED + "!=0");
    221                 break;
    222             }
    223             case ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY: {
    224                 selection.append(Contacts.HAS_PHONE_NUMBER + "=1");
    225                 break;
    226             }
    227             case ContactListFilter.FILTER_TYPE_CUSTOM: {
    228                 selection.append(Contacts.IN_VISIBLE_GROUP + "=1");
    229                 if (isCustomFilterForPhoneNumbersOnly()) {
    230                     selection.append(" AND " + Contacts.HAS_PHONE_NUMBER + "=1");
    231                 }
    232                 break;
    233             }
    234             case ContactListFilter.FILTER_TYPE_ACCOUNT: {
    235                 // We use query parameters for account filter, so no selection to add here.
    236                 break;
    237             }
    238             case ContactListFilter.FILTER_TYPE_GROUP_MEMBERS: {
    239                 break;
    240             }
    241             case ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS: {
    242                 if (filter.accountType != null) {
    243                     selection.append(ContactsContract.RawContacts.ACCOUNT_TYPE)
    244                             .append("=?");
    245                     selectionArgs.add(filter.accountType);
    246                     if (filter.accountName != null) {
    247                         selection.append(" AND ").append(ContactsContract.RawContacts.ACCOUNT_NAME)
    248                                 .append(("=?"));
    249                         selectionArgs.add(filter.accountName);
    250                     }
    251                 } else {
    252                     selection.append(AccountWithDataSet.LOCAL_ACCOUNT_SELECTION);
    253                 }
    254                 break;
    255             }
    256         }
    257         loader.setSelection(selection.toString());
    258         loader.setSelectionArgs(selectionArgs.toArray(new String[0]));
    259     }
    260 
    261     @Override
    262     protected void bindView(View itemView, int partition, Cursor cursor, int position) {
    263         super.bindView(itemView, partition, cursor, position);
    264         final ContactListItemView view = (ContactListItemView)itemView;
    265 
    266         view.setHighlightedPrefix(isSearchMode() ? getUpperCaseQueryString() : null);
    267 
    268         if (isSelectionVisible()) {
    269             view.setActivated(isSelectedContact(partition, cursor));
    270         }
    271 
    272         bindSectionHeaderAndDivider(view, position, cursor);
    273 
    274         if (isQuickContactEnabled()) {
    275             bindQuickContact(view, partition, cursor, ContactQuery.CONTACT_PHOTO_ID,
    276                     ContactQuery.CONTACT_PHOTO_URI, ContactQuery.CONTACT_ID,
    277                     ContactQuery.CONTACT_LOOKUP_KEY, ContactQuery.CONTACT_DISPLAY_NAME);
    278         } else {
    279             if (getDisplayPhotos()) {
    280                 bindPhoto(view, partition, cursor);
    281             }
    282         }
    283 
    284         bindNameAndViewId(view, cursor);
    285         bindPresenceAndStatusMessage(view, cursor);
    286 
    287         if (isSearchMode()) {
    288             bindSearchSnippet(view, cursor);
    289         } else {
    290             view.setSnippet(null);
    291         }
    292     }
    293 
    294     private boolean isCustomFilterForPhoneNumbersOnly() {
    295         // TODO: this flag should not be stored in shared prefs.  It needs to be in the db.
    296         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
    297         return prefs.getBoolean(ContactsPreferences.PREF_DISPLAY_ONLY_PHONES,
    298                 ContactsPreferences.PREF_DISPLAY_ONLY_PHONES_DEFAULT);
    299     }
    300 }
    301