1 /*
2  * Copyright (C) 2014 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 
17 package com.android.settings.search;
18 
19 import android.content.Context;
20 import android.database.Cursor;
21 import android.database.sqlite.SQLiteDatabase;
22 import android.database.sqlite.SQLiteOpenHelper;
23 import android.os.Build;
24 import android.util.Log;
25 
26 public class IndexDatabaseHelper extends SQLiteOpenHelper {
27 
28     private static final String TAG = "IndexDatabaseHelper";
29 
30     private static final String DATABASE_NAME = "search_index.db";
31     private static final int DATABASE_VERSION = 115;
32 
33     private static final String INDEX = "index";
34 
35     public interface Tables {
36         public static final String TABLE_PREFS_INDEX = "prefs_index";
37         public static final String TABLE_META_INDEX = "meta_index";
38         public static final String TABLE_SAVED_QUERIES = "saved_queries";
39     }
40 
41     public interface IndexColumns {
42         public static final String DOCID = "docid";
43         public static final String LOCALE = "locale";
44         public static final String DATA_RANK = "data_rank";
45         public static final String DATA_TITLE = "data_title";
46         public static final String DATA_TITLE_NORMALIZED = "data_title_normalized";
47         public static final String DATA_SUMMARY_ON = "data_summary_on";
48         public static final String DATA_SUMMARY_ON_NORMALIZED = "data_summary_on_normalized";
49         public static final String DATA_SUMMARY_OFF = "data_summary_off";
50         public static final String DATA_SUMMARY_OFF_NORMALIZED = "data_summary_off_normalized";
51         public static final String DATA_ENTRIES = "data_entries";
52         public static final String DATA_KEYWORDS = "data_keywords";
53         public static final String CLASS_NAME = "class_name";
54         public static final String SCREEN_TITLE = "screen_title";
55         public static final String INTENT_ACTION = "intent_action";
56         public static final String INTENT_TARGET_PACKAGE = "intent_target_package";
57         public static final String INTENT_TARGET_CLASS = "intent_target_class";
58         public static final String ICON = "icon";
59         public static final String ENABLED = "enabled";
60         public static final String DATA_KEY_REF = "data_key_reference";
61         public static final String USER_ID = "user_id";
62     }
63 
64     public interface MetaColumns {
65         public static final String BUILD = "build";
66     }
67 
68     public interface SavedQueriesColums {
69         public static final String QUERY = "query";
70         public static final String TIME_STAMP = "timestamp";
71     }
72 
73     private static final String CREATE_INDEX_TABLE =
74             "CREATE VIRTUAL TABLE " + Tables.TABLE_PREFS_INDEX + " USING fts4" +
75                     "(" +
76                     IndexColumns.LOCALE +
77                     ", " +
78                     IndexColumns.DATA_RANK +
79                     ", " +
80                     IndexColumns.DATA_TITLE +
81                     ", " +
82                     IndexColumns.DATA_TITLE_NORMALIZED +
83                     ", " +
84                     IndexColumns.DATA_SUMMARY_ON +
85                     ", " +
86                     IndexColumns.DATA_SUMMARY_ON_NORMALIZED +
87                     ", " +
88                     IndexColumns.DATA_SUMMARY_OFF +
89                     ", " +
90                     IndexColumns.DATA_SUMMARY_OFF_NORMALIZED +
91                     ", " +
92                     IndexColumns.DATA_ENTRIES +
93                     ", " +
94                     IndexColumns.DATA_KEYWORDS +
95                     ", " +
96                     IndexColumns.SCREEN_TITLE +
97                     ", " +
98                     IndexColumns.CLASS_NAME +
99                     ", " +
100                     IndexColumns.ICON +
101                     ", " +
102                     IndexColumns.INTENT_ACTION +
103                     ", " +
104                     IndexColumns.INTENT_TARGET_PACKAGE +
105                     ", " +
106                     IndexColumns.INTENT_TARGET_CLASS +
107                     ", " +
108                     IndexColumns.ENABLED +
109                     ", " +
110                     IndexColumns.DATA_KEY_REF +
111                     ", " +
112                     IndexColumns.USER_ID +
113                     ");";
114 
115     private static final String CREATE_META_TABLE =
116             "CREATE TABLE " + Tables.TABLE_META_INDEX +
117                     "(" +
118                     MetaColumns.BUILD + " VARCHAR(32) NOT NULL" +
119                     ")";
120 
121     private static final String CREATE_SAVED_QUERIES_TABLE =
122             "CREATE TABLE " + Tables.TABLE_SAVED_QUERIES +
123                     "(" +
124                     SavedQueriesColums.QUERY + " VARCHAR(64) NOT NULL" +
125                     ", " +
126                     SavedQueriesColums.TIME_STAMP + " INTEGER" +
127                     ")";
128 
129     private static final String INSERT_BUILD_VERSION =
130             "INSERT INTO " + Tables.TABLE_META_INDEX +
131                     " VALUES ('" + Build.VERSION.INCREMENTAL + "');";
132 
133     private static final String SELECT_BUILD_VERSION =
134             "SELECT " + MetaColumns.BUILD + " FROM " + Tables.TABLE_META_INDEX + " LIMIT 1;";
135 
136     private static IndexDatabaseHelper sSingleton;
137 
138     private final Context mContext;
139 
getInstance(Context context)140     public static synchronized IndexDatabaseHelper getInstance(Context context) {
141         if (sSingleton == null) {
142             sSingleton = new IndexDatabaseHelper(context);
143         }
144         return sSingleton;
145     }
146 
IndexDatabaseHelper(Context context)147     public IndexDatabaseHelper(Context context) {
148         super(context, DATABASE_NAME, null, DATABASE_VERSION);
149         mContext = context;
150     }
151 
152     @Override
onCreate(SQLiteDatabase db)153     public void onCreate(SQLiteDatabase db) {
154         bootstrapDB(db);
155     }
156 
bootstrapDB(SQLiteDatabase db)157     private void bootstrapDB(SQLiteDatabase db) {
158         db.execSQL(CREATE_INDEX_TABLE);
159         db.execSQL(CREATE_META_TABLE);
160         db.execSQL(CREATE_SAVED_QUERIES_TABLE);
161         db.execSQL(INSERT_BUILD_VERSION);
162         Log.i(TAG, "Bootstrapped database");
163     }
164 
165     @Override
onOpen(SQLiteDatabase db)166     public void onOpen(SQLiteDatabase db) {
167         super.onOpen(db);
168 
169         Log.i(TAG, "Using schema version: " + db.getVersion());
170 
171         if (!Build.VERSION.INCREMENTAL.equals(getBuildVersion(db))) {
172             Log.w(TAG, "Index needs to be rebuilt as build-version is not the same");
173             // We need to drop the tables and recreate them
174             reconstruct(db);
175         } else {
176             Log.i(TAG, "Index is fine");
177         }
178     }
179 
180     @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)181     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
182         if (oldVersion < DATABASE_VERSION) {
183             Log.w(TAG, "Detected schema version '" + oldVersion + "'. " +
184                     "Index needs to be rebuilt for schema version '" + newVersion + "'.");
185             // We need to drop the tables and recreate them
186             reconstruct(db);
187         }
188     }
189 
190     @Override
onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)191     public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
192         Log.w(TAG, "Detected schema version '" + oldVersion + "'. " +
193                 "Index needs to be rebuilt for schema version '" + newVersion + "'.");
194         // We need to drop the tables and recreate them
195         reconstruct(db);
196     }
197 
reconstruct(SQLiteDatabase db)198     private void reconstruct(SQLiteDatabase db) {
199         dropTables(db);
200         bootstrapDB(db);
201     }
202 
getBuildVersion(SQLiteDatabase db)203     private String getBuildVersion(SQLiteDatabase db) {
204         String version = null;
205         Cursor cursor = null;
206         try {
207             cursor = db.rawQuery(SELECT_BUILD_VERSION, null);
208             if (cursor.moveToFirst()) {
209                 version = cursor.getString(0);
210             }
211         } catch (Exception e) {
212             Log.e(TAG, "Cannot get build version from Index metadata");
213         } finally {
214             if (cursor != null) {
215                 cursor.close();
216             }
217         }
218         return version;
219     }
220 
clearLocalesIndexed(Context context)221     public static void clearLocalesIndexed(Context context) {
222         context.getSharedPreferences(INDEX, 0).edit().clear().commit();
223     }
224 
setLocaleIndexed(Context context, String locale)225     public static void setLocaleIndexed(Context context, String locale) {
226         context.getSharedPreferences(INDEX, 0).edit().putBoolean(locale, true).commit();
227     }
228 
isLocaleAlreadyIndexed(Context context, String locale)229     public static boolean isLocaleAlreadyIndexed(Context context, String locale) {
230         return context.getSharedPreferences(INDEX, 0).getBoolean(locale, false);
231     }
232 
dropTables(SQLiteDatabase db)233     private void dropTables(SQLiteDatabase db) {
234         clearLocalesIndexed(mContext);
235         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_META_INDEX);
236         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_PREFS_INDEX);
237         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES);
238     }
239 }
240