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