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.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 
     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 
     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 {
     97         public DatabaseHelper(Context context, int newVersion) {
     98             super(context, DATABASE_NAME, null, newVersion);
     99         }
    100 
    101         @Override
    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
    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      */
    131     public void setFullQueryTerms(ArrayList<String> terms) {
    132         mFullQueryTerms = terms;
    133     }
    134 
    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 
    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      */
    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 
    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 }