• 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.app.SearchManager;
21 import android.content.ContentResolver;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.database.Cursor;
25 import android.database.sqlite.SQLiteDatabase;
26 import android.database.sqlite.SQLiteOpenHelper;
27 import android.support.annotation.Nullable;
28 
29 import com.android.mail.R;
30 
31 import java.util.ArrayList;
32 
33 public class SearchRecentSuggestionsProvider {
34     /*
35      * String used to delimit different parts of a query.
36      */
37     public static final String QUERY_TOKEN_SEPARATOR = " ";
38 
39     // general database configuration and tables
40     private SQLiteOpenHelper mOpenHelper;
41     private static final String DATABASE_NAME = "suggestions.db";
42     private static final String SUGGESTIONS_TABLE = "suggestions";
43 
44     private static final String QUERY =
45             " SELECT _id" +
46             "   , display1 AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 +
47             "   , ? || query AS " + SearchManager.SUGGEST_COLUMN_QUERY +
48             "   , ? AS " + SearchManager.SUGGEST_COLUMN_ICON_1 +
49             " FROM " + SUGGESTIONS_TABLE +
50             " WHERE display1 LIKE ?" +
51             " ORDER BY date DESC";
52 
53     // Table of database versions.  Don't forget to update!
54     // NOTE:  These version values are shifted left 8 bits (x 256) in order to create space for
55     // a small set of mode bitflags in the version int.
56     //
57     // 1      original implementation with queries, and 1 or 2 display columns
58     // 1->2   added UNIQUE constraint to display1 column
59     // 2->3   <redacted> being dumb and accidentally upgraded, this should be ignored.
60     private static final int DATABASE_VERSION = 3 * 256;
61 
62     private static final int DATABASE_VERSION_2 = 2 * 256;
63     private static final int DATABASE_VERSION_3 = 3 * 256;
64 
65     private String mHistoricalIcon;
66 
67     protected final Context mContext;
68     private ArrayList<String> mFullQueryTerms;
69 
70     private final Object mDbLock = new Object();
71     private boolean mClosed;
72 
SearchRecentSuggestionsProvider(Context context)73     public SearchRecentSuggestionsProvider(Context context) {
74         mContext = context;
75         mOpenHelper = new DatabaseHelper(mContext, DATABASE_VERSION);
76 
77         // The URI of the icon that we will include on every suggestion here.
78         mHistoricalIcon = ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
79                 + mContext.getPackageName() + "/" + R.drawable.ic_history_24dp;
80     }
81 
cleanup()82     public void cleanup() {
83         synchronized (mDbLock) {
84             mOpenHelper.close();
85             mClosed = true;
86         }
87     }
88 
89     /**
90      * Builds the database.  This version has extra support for using the version field
91      * as a mode flags field, and configures the database columns depending on the mode bits
92      * (features) requested by the extending class.
93      *
94      * @hide
95      */
96     private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context, int newVersion)97         public DatabaseHelper(Context context, int newVersion) {
98             super(context, DATABASE_NAME, null, newVersion);
99         }
100 
101         @Override
onCreate(SQLiteDatabase db)102         public void onCreate(SQLiteDatabase db) {
103             final String create = "CREATE TABLE suggestions (" +
104                     "_id INTEGER PRIMARY KEY" +
105                     ",display1 TEXT UNIQUE ON CONFLICT REPLACE" +
106                     ",query TEXT" +
107                     ",date LONG" +
108                     ");";
109             db.execSQL(create);
110         }
111 
112         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)113         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
114             // When checking the old version clear the last 8 bits
115             oldVersion = oldVersion & ~0xff;
116             newVersion = newVersion & ~0xff;
117             if (oldVersion == DATABASE_VERSION_2 && newVersion == DATABASE_VERSION_3) {
118                 // Oops, didn't mean to upgrade this database. Ignore this upgrade.
119                 return;
120             }
121             db.execSQL("DROP TABLE IF EXISTS suggestions");
122             onCreate(db);
123         }
124     }
125 
126     /**
127      * Set the other query terms to be included in the user's query.
128      * These are in addition to what is being looked up for suggestions.
129      * @param terms
130      */
setFullQueryTerms(ArrayList<String> terms)131     public void setFullQueryTerms(ArrayList<String> terms) {
132         mFullQueryTerms = terms;
133     }
134 
getDatabase(boolean readOnly)135     private @Nullable SQLiteDatabase getDatabase(boolean readOnly) {
136         synchronized (mDbLock) {
137             if (!mClosed) {
138                 return readOnly ? mOpenHelper.getReadableDatabase() :
139                         mOpenHelper.getWritableDatabase();
140             }
141         }
142         return null;
143     }
144 
query(String query)145     public Cursor query(String query) {
146         final SQLiteDatabase db = getDatabase(true /* readOnly */);
147         if (db != null) {
148             final StringBuilder builder = new StringBuilder();
149             if (mFullQueryTerms != null) {
150                 for (String token : mFullQueryTerms) {
151                     builder.append(token).append(QUERY_TOKEN_SEPARATOR);
152                 }
153             }
154 
155             final String[] args = new String[] {
156                     builder.toString(), mHistoricalIcon, "%" + query + "%" };
157 
158             try {
159                 // db could have been closed due to cleanup, simply don't do anything.
160                 return db.rawQuery(QUERY, args);
161             } catch (IllegalStateException e) {}
162         }
163         return null;
164     }
165 
166     /**
167      * We are going to keep track of recent suggestions ourselves and not depend on the framework.
168      * Note that this writes to disk. DO NOT CALL FROM MAIN THREAD.
169      */
saveRecentQuery(String query)170     public void saveRecentQuery(String query) {
171         final SQLiteDatabase db = getDatabase(false /* readOnly */);
172         if (db != null) {
173             ContentValues values = new ContentValues(3);
174             values.put("display1", query);
175             values.put("query", query);
176             values.put("date", System.currentTimeMillis());
177             // Note:  This table has on-conflict-replace semantics, so insert may actually replace
178             try {
179                 // db could have been closed due to cleanup, simply don't do anything.
180                 db.insert(SUGGESTIONS_TABLE, null, values);
181             } catch (IllegalStateException e) {}
182         }
183     }
184 
clearHistory()185     public void clearHistory() {
186         final SQLiteDatabase db = getDatabase(false /* readOnly */);
187         if (db != null) {
188             try {
189                 // db could have been closed due to cleanup, simply don't do anything.
190                 db.delete(SUGGESTIONS_TABLE, null, null);
191             } catch (IllegalStateException e) {}
192         }
193     }
194 }