• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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. The authority fro for this provider is obtained
39  * through the MailAppProvider as follows:
40  * final String AUTHORITY = MailAppProvider.getInstance().getSuggestionAuthority()
41  * It needs to be done after the MailAppProvider is constructed.
42  */
43 public class SuggestionsProvider extends SearchRecentSuggestionsProvider {
44     /**
45      * Mode used in the constructor of SuggestionsProvider.
46      */
47     public final static int MODE = DATABASE_MODE_QUERIES;
48     /**
49      * Columns over the contacts database that we return in the {@link ContactsCursor}.
50      */
51     private static final String[] CONTACTS_COLUMNS = new String[] {
52             BaseColumns._ID,
53             SearchManager.SUGGEST_COLUMN_TEXT_1, SearchManager.SUGGEST_COLUMN_QUERY,
54             SearchManager.SUGGEST_COLUMN_ICON_1
55     };
56     private ArrayList<String> mFullQueryTerms;
57     /** Used for synchronization */
58     private final Object mTermsLock = new Object();
59     private final static String[] sContract = new String[] {
60             ContactsContract.CommonDataKinds.Email.DISPLAY_NAME,
61             ContactsContract.CommonDataKinds.Email.DATA
62     };
63     /**
64      * Minimum length of query before we start showing contacts suggestions.
65      */
66     static private final int MIN_QUERY_LENGTH_FOR_CONTACTS = 2;
67 
SuggestionsProvider()68     public SuggestionsProvider() {
69         super();
70     }
71 
72     @Override
onCreate()73     public boolean onCreate() {
74         final String authority = getContext().getString(R.string.suggestions_authority);
75         setupSuggestions(authority, MODE);
76         super.onCreate();
77         return true;
78     }
79 
80     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)81     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
82             String sortOrder) {
83         String query = selectionArgs[0];
84         MergeCursor mergeCursor = null;
85 
86         synchronized (mTermsLock) {
87             mFullQueryTerms = null;
88             super.setFullQueryTerms(mFullQueryTerms);
89         }
90         // Get the custom suggestions for email which are from, to, etc.
91         if (query != null) {
92             // Tokenize the query.
93             String[] tokens = TextUtils.split(query,
94                     SearchRecentSuggestionsProvider.QUERY_TOKEN_SEPARATOR);
95             // There are multiple tokens, so query on the last token only.
96             if (tokens != null && tokens.length > 1) {
97                 query = tokens[tokens.length - 1];
98                 // Leave off the last token since we are auto completing on it.
99                 synchronized (mTermsLock) {
100                     mFullQueryTerms = new ArrayList<String>();
101                     for (int i = 0, size = tokens.length - 1; i < size; i++) {
102                         mFullQueryTerms.add(tokens[i]);
103                     }
104                     super.setFullQueryTerms(mFullQueryTerms);
105                 }
106             } else {
107                 // Strip excess whitespace.
108                 query = query.trim();
109             }
110             ArrayList<Cursor> cursors = new ArrayList<Cursor>();
111             // Pass query; at this point it is either the last term OR the
112             // only term.
113             cursors.add(super.query(uri, projection, selection, new String[] { query }, sortOrder));
114 
115             if (query.length() >= MIN_QUERY_LENGTH_FOR_CONTACTS) {
116                 cursors.add(new ContactsCursor().query(query));
117             }
118             mergeCursor = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
119         }
120         return mergeCursor;
121     }
122 
123     /**
124      * Utility class to return a cursor over the contacts database
125      */
126     private final class ContactsCursor extends MatrixCursorWithCachedColumns {
127         private final Context mContext;
ContactsCursor()128         public ContactsCursor() {
129             super(CONTACTS_COLUMNS);
130             mContext = getContext();
131         }
132 
133         /**
134          * Searches over the contacts cursor with the specified query as the starting characters to
135          * match.
136          * @param query
137          * @return a cursor over the contacts database with the contacts matching the query.
138          */
query(String query)139         public ContactsCursor query(String query) {
140             final Uri contactsUri = Uri.withAppendedPath(
141                     ContactsContract.CommonDataKinds.Email.CONTENT_FILTER_URI, Uri.encode(query));
142             final Cursor cursor = mContext.getContentResolver().query(
143                     contactsUri, sContract, null, null, null);
144             // We don't want to show a contact icon here. Leaving the SEARCH_ICON_1 field
145             // empty causes inconsistent behavior because the cursor is merged with the
146             // historical suggestions, which have an icon.  The solution is to show an empty icon
147             // instead.
148             final String emptyIcon = ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
149                     + mContext.getPackageName() + "/" + R.drawable.empty;
150             if (cursor != null) {
151                 final int nameIndex = cursor
152                         .getColumnIndex(ContactsContract.CommonDataKinds.Email.DISPLAY_NAME);
153                 final int addressIndex = cursor
154                         .getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA);
155                 String match;
156                 while (cursor.moveToNext()) {
157                     match = cursor.getString(nameIndex);
158                     match = !TextUtils.isEmpty(match) ? match : cursor.getString(addressIndex);
159                     // The order of fields is:
160                     // _ID, SUGGEST_COLUMN_TEXT_1, SUGGEST_COLUMN_QUERY, SUGGEST_COLUMN_ICON_1
161                     addRow(new Object[] {0, match, createQuery(match), emptyIcon});
162                 }
163                 cursor.close();
164             }
165             return this;
166         }
167     }
168 
createQuery(String inMatch)169     private String createQuery(String inMatch) {
170         final StringBuilder query = new StringBuilder();
171         if (mFullQueryTerms != null) {
172             synchronized (mTermsLock) {
173                 for (int i = 0, size = mFullQueryTerms.size(); i < size; i++) {
174                     query.append(mFullQueryTerms.get(i)).append(QUERY_TOKEN_SEPARATOR);
175                 }
176             }
177         }
178         // Append the match as well.
179         query.append(inMatch);
180         // Example:
181         // Search terms in the searchbox are : "pdf test*"
182         // Contacts database contains: test@tester.com, test@other.com
183         // If the user taps "test@tester.com", the query passed with
184         // ACTION_SEARCH is:
185         // "pdf test@tester.com"
186         // If the user taps "test@other.com", the query passed with
187         // ACTION_SEARCH is:
188         // "pdf test@other.com"
189         return query.toString();
190     }
191 }