Home | History | Annotate | Download | only in providers
      1 /*******************************************************************************
      2  *      Copyright (C) 2012 Google Inc.
      3  *      Licensed to The Android Open Source Project.
      4  *
      5  *      Licensed under the Apache License, Version 2.0 (the "License");
      6  *      you may not use this file except in compliance with the License.
      7  *      You may obtain a copy of the License at
      8  *
      9  *           http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *      Unless required by applicable law or agreed to in writing, software
     12  *      distributed under the License is distributed on an "AS IS" BASIS,
     13  *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *      See the License for the specific language governing permissions and
     15  *      limitations under the License.
     16  *******************************************************************************/
     17 
     18 package com.android.mail.providers;
     19 
     20 import android.database.Cursor;
     21 import android.database.MergeCursor;
     22 import android.net.Uri;
     23 import android.provider.BaseColumns;
     24 import android.provider.ContactsContract;
     25 import android.app.SearchManager;
     26 import android.content.ContentResolver;
     27 import android.content.Context;
     28 import android.text.TextUtils;
     29 
     30 import com.android.mail.R;
     31 import com.android.mail.utils.MatrixCursorWithCachedColumns;
     32 
     33 import java.util.ArrayList;
     34 
     35 /**
     36  * Simple extension / instantiation of SearchRecentSuggestionsProvider, independent
     37  * of mail account or account capabilities.  Offers suggestions from historical searches
     38  * and contact email addresses on the device.
     39  */
     40 public class SuggestionsProvider extends SearchRecentSuggestionsProvider {
     41     /**
     42      * Columns over the contacts database that we return in the {@link ContactsCursor}.
     43      */
     44     private static final String[] CONTACTS_COLUMNS = new String[] {
     45             BaseColumns._ID,
     46             SearchManager.SUGGEST_COLUMN_TEXT_1, SearchManager.SUGGEST_COLUMN_QUERY,
     47             SearchManager.SUGGEST_COLUMN_ICON_1
     48     };
     49     private ArrayList<String> mFullQueryTerms;
     50     /** Used for synchronization */
     51     private final Object mTermsLock = new Object();
     52     private final static String[] sContract = new String[] {
     53             ContactsContract.CommonDataKinds.Email.DISPLAY_NAME,
     54             ContactsContract.CommonDataKinds.Email.DATA
     55     };
     56     /**
     57      * Minimum length of query before we start showing contacts suggestions.
     58      */
     59     static private final int MIN_QUERY_LENGTH_FOR_CONTACTS = 2;
     60 
     61     public SuggestionsProvider(Context context) {
     62         super(context);
     63     }
     64 
     65     @Override
     66     public Cursor query(String query) {
     67         Cursor mergeCursor = null;
     68 
     69         synchronized (mTermsLock) {
     70             mFullQueryTerms = null;
     71             super.setFullQueryTerms(mFullQueryTerms);
     72         }
     73         // Get the custom suggestions for email which are from, to, etc.
     74         if (query != null) {
     75             // Tokenize the query.
     76             String[] tokens = TextUtils.split(query,
     77                     SearchRecentSuggestionsProvider.QUERY_TOKEN_SEPARATOR);
     78             // There are multiple tokens, so query on the last token only.
     79             if (tokens != null && tokens.length > 1) {
     80                 query = tokens[tokens.length - 1];
     81                 // Leave off the last token since we are auto completing on it.
     82                 synchronized (mTermsLock) {
     83                     mFullQueryTerms = new ArrayList<String>();
     84                     for (int i = 0, size = tokens.length - 1; i < size; i++) {
     85                         mFullQueryTerms.add(tokens[i]);
     86                     }
     87                     super.setFullQueryTerms(mFullQueryTerms);
     88                 }
     89             } else {
     90                 // Strip excess whitespace.
     91                 query = query.trim();
     92             }
     93             ArrayList<Cursor> cursors = new ArrayList<Cursor>();
     94             // Pass query; at this point it is either the last term OR the
     95             // only term.
     96             final Cursor c = super.query(query);
     97             if (c != null) {
     98                 cursors.add(c);
     99             }
    100 
    101             if (query.length() >= MIN_QUERY_LENGTH_FOR_CONTACTS) {
    102                 cursors.add(new ContactsCursor().query(query));
    103             }
    104 
    105             if (cursors.size() > 0) {
    106                 mergeCursor = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
    107             }
    108         }
    109         return mergeCursor;
    110     }
    111 
    112     /**
    113      * Utility class to return a cursor over the contacts database
    114      */
    115     private final class ContactsCursor extends MatrixCursorWithCachedColumns {
    116         public ContactsCursor() {
    117             super(CONTACTS_COLUMNS);
    118         }
    119 
    120         /**
    121          * Searches over the contacts cursor with the specified query as the starting characters to
    122          * match.
    123          * @param query
    124          * @return a cursor over the contacts database with the contacts matching the query.
    125          */
    126         public ContactsCursor query(String query) {
    127             final Uri contactsUri = Uri.withAppendedPath(
    128                     ContactsContract.CommonDataKinds.Email.CONTENT_FILTER_URI, Uri.encode(query));
    129             final Cursor cursor = mContext.getContentResolver().query(
    130                     contactsUri, sContract, null, null, null);
    131             // We don't want to show a contact icon here. Leaving the SEARCH_ICON_1 field
    132             // empty causes inconsistent behavior because the cursor is merged with the
    133             // historical suggestions, which have an icon.  The solution is to show an empty icon
    134             // instead.
    135             final String emptyIcon = ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
    136                     + mContext.getPackageName() + "/" + R.drawable.empty;
    137             if (cursor != null) {
    138                 final int nameIndex = cursor
    139                         .getColumnIndex(ContactsContract.CommonDataKinds.Email.DISPLAY_NAME);
    140                 final int addressIndex = cursor
    141                         .getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA);
    142                 String match;
    143                 while (cursor.moveToNext()) {
    144                     match = cursor.getString(nameIndex);
    145                     match = !TextUtils.isEmpty(match) ? match : cursor.getString(addressIndex);
    146                     // The order of fields is:
    147                     // _ID, SUGGEST_COLUMN_TEXT_1, SUGGEST_COLUMN_QUERY, SUGGEST_COLUMN_ICON_1
    148                     addRow(new Object[] {0, match, createQuery(match), emptyIcon});
    149                 }
    150                 cursor.close();
    151             }
    152             return this;
    153         }
    154     }
    155 
    156     private String createQuery(String inMatch) {
    157         final StringBuilder query = new StringBuilder();
    158         if (mFullQueryTerms != null) {
    159             synchronized (mTermsLock) {
    160                 for (String token : mFullQueryTerms) {
    161                     query.append(token).append(QUERY_TOKEN_SEPARATOR);
    162                 }
    163             }
    164         }
    165         // Append the match as well.
    166         query.append(inMatch);
    167         // Example:
    168         // Search terms in the searchbox are : "pdf test*"
    169         // Contacts database contains: test (at) tester.com, test (at) other.com
    170         // If the user taps "test (at) tester.com", the query passed with
    171         // ACTION_SEARCH is:
    172         // "pdf test (at) tester.com"
    173         // If the user taps "test (at) other.com", the query passed with
    174         // ACTION_SEARCH is:
    175         // "pdf test (at) other.com"
    176         return query.toString();
    177     }
    178 }