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