• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.pm.ResolveInfo;
21 import android.database.Cursor;
22 import android.database.sqlite.SQLiteDatabase;
23 import android.database.sqlite.SQLiteOpenHelper;
24 import android.os.Build;
25 import android.support.annotation.VisibleForTesting;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import java.util.List;
30 
31 public class IndexDatabaseHelper extends SQLiteOpenHelper {
32 
33     private static final String TAG = "IndexDatabaseHelper";
34 
35     private static final String DATABASE_NAME = "search_index.db";
36     private static final int DATABASE_VERSION = 117;
37 
38     private static final String INDEX = "index";
39 
40     private static final String PREF_KEY_INDEXED_PROVIDERS = "indexed_providers";
41 
42     public interface Tables {
43         String TABLE_PREFS_INDEX = "prefs_index";
44         String TABLE_SITE_MAP = "site_map";
45         String TABLE_META_INDEX = "meta_index";
46         String TABLE_SAVED_QUERIES = "saved_queries";
47     }
48 
49     public interface IndexColumns {
50         String DOCID = "docid";
51         String LOCALE = "locale";
52         String DATA_RANK = "data_rank";
53         String DATA_TITLE = "data_title";
54         String DATA_TITLE_NORMALIZED = "data_title_normalized";
55         String DATA_SUMMARY_ON = "data_summary_on";
56         String DATA_SUMMARY_ON_NORMALIZED = "data_summary_on_normalized";
57         String DATA_SUMMARY_OFF = "data_summary_off";
58         String DATA_SUMMARY_OFF_NORMALIZED = "data_summary_off_normalized";
59         String DATA_ENTRIES = "data_entries";
60         String DATA_KEYWORDS = "data_keywords";
61         String CLASS_NAME = "class_name";
62         String SCREEN_TITLE = "screen_title";
63         String INTENT_ACTION = "intent_action";
64         String INTENT_TARGET_PACKAGE = "intent_target_package";
65         String INTENT_TARGET_CLASS = "intent_target_class";
66         String ICON = "icon";
67         String ENABLED = "enabled";
68         String DATA_KEY_REF = "data_key_reference";
69         String USER_ID = "user_id";
70         String PAYLOAD_TYPE = "payload_type";
71         String PAYLOAD = "payload";
72     }
73 
74     public interface MetaColumns {
75         String BUILD = "build";
76     }
77 
78     public interface SavedQueriesColumns {
79         String QUERY = "query";
80         String TIME_STAMP = "timestamp";
81     }
82 
83     public interface SiteMapColumns {
84         String DOCID = "docid";
85         String PARENT_CLASS = "parent_class";
86         String CHILD_CLASS = "child_class";
87         String PARENT_TITLE = "parent_title";
88         String CHILD_TITLE = "child_title";
89     }
90 
91     private static final String CREATE_INDEX_TABLE =
92             "CREATE VIRTUAL TABLE " + Tables.TABLE_PREFS_INDEX + " USING fts4" +
93                     "(" +
94                     IndexColumns.LOCALE +
95                     ", " +
96                     IndexColumns.DATA_RANK +
97                     ", " +
98                     IndexColumns.DATA_TITLE +
99                     ", " +
100                     IndexColumns.DATA_TITLE_NORMALIZED +
101                     ", " +
102                     IndexColumns.DATA_SUMMARY_ON +
103                     ", " +
104                     IndexColumns.DATA_SUMMARY_ON_NORMALIZED +
105                     ", " +
106                     IndexColumns.DATA_SUMMARY_OFF +
107                     ", " +
108                     IndexColumns.DATA_SUMMARY_OFF_NORMALIZED +
109                     ", " +
110                     IndexColumns.DATA_ENTRIES +
111                     ", " +
112                     IndexColumns.DATA_KEYWORDS +
113                     ", " +
114                     IndexColumns.SCREEN_TITLE +
115                     ", " +
116                     IndexColumns.CLASS_NAME +
117                     ", " +
118                     IndexColumns.ICON +
119                     ", " +
120                     IndexColumns.INTENT_ACTION +
121                     ", " +
122                     IndexColumns.INTENT_TARGET_PACKAGE +
123                     ", " +
124                     IndexColumns.INTENT_TARGET_CLASS +
125                     ", " +
126                     IndexColumns.ENABLED +
127                     ", " +
128                     IndexColumns.DATA_KEY_REF +
129                     ", " +
130                     IndexColumns.USER_ID +
131                     ", " +
132                     IndexColumns.PAYLOAD_TYPE +
133                     ", " +
134                     IndexColumns.PAYLOAD +
135                     ");";
136 
137     private static final String CREATE_META_TABLE =
138             "CREATE TABLE " + Tables.TABLE_META_INDEX +
139                     "(" +
140                     MetaColumns.BUILD + " VARCHAR(32) NOT NULL" +
141                     ")";
142 
143     private static final String CREATE_SAVED_QUERIES_TABLE =
144             "CREATE TABLE " + Tables.TABLE_SAVED_QUERIES +
145                     "(" +
146                     SavedQueriesColumns.QUERY + " VARCHAR(64) NOT NULL" +
147                     ", " +
148                     SavedQueriesColumns.TIME_STAMP + " INTEGER" +
149                     ")";
150 
151     private static final String CREATE_SITE_MAP_TABLE =
152             "CREATE VIRTUAL TABLE " + Tables.TABLE_SITE_MAP + " USING fts4" +
153                     "(" +
154                     SiteMapColumns.PARENT_CLASS +
155                     ", " +
156                     SiteMapColumns.CHILD_CLASS +
157                     ", " +
158                     SiteMapColumns.PARENT_TITLE +
159                     ", " +
160                     SiteMapColumns.CHILD_TITLE +
161                     ")";
162     private static final String INSERT_BUILD_VERSION =
163             "INSERT INTO " + Tables.TABLE_META_INDEX +
164                     " VALUES ('" + Build.VERSION.INCREMENTAL + "');";
165 
166     private static final String SELECT_BUILD_VERSION =
167             "SELECT " + MetaColumns.BUILD + " FROM " + Tables.TABLE_META_INDEX + " LIMIT 1;";
168 
169     private static IndexDatabaseHelper sSingleton;
170 
171     private final Context mContext;
172 
getInstance(Context context)173     public static synchronized IndexDatabaseHelper getInstance(Context context) {
174         if (sSingleton == null) {
175             sSingleton = new IndexDatabaseHelper(context);
176         }
177         return sSingleton;
178     }
179 
IndexDatabaseHelper(Context context)180     public IndexDatabaseHelper(Context context) {
181         super(context, DATABASE_NAME, null, DATABASE_VERSION);
182         mContext = context;
183     }
184 
185     @Override
onCreate(SQLiteDatabase db)186     public void onCreate(SQLiteDatabase db) {
187         bootstrapDB(db);
188     }
189 
bootstrapDB(SQLiteDatabase db)190     private void bootstrapDB(SQLiteDatabase db) {
191         db.execSQL(CREATE_INDEX_TABLE);
192         db.execSQL(CREATE_META_TABLE);
193         db.execSQL(CREATE_SAVED_QUERIES_TABLE);
194         db.execSQL(CREATE_SITE_MAP_TABLE);
195         db.execSQL(INSERT_BUILD_VERSION);
196         Log.i(TAG, "Bootstrapped database");
197     }
198 
199     @Override
onOpen(SQLiteDatabase db)200     public void onOpen(SQLiteDatabase db) {
201         super.onOpen(db);
202 
203         Log.i(TAG, "Using schema version: " + db.getVersion());
204 
205         if (!Build.VERSION.INCREMENTAL.equals(getBuildVersion(db))) {
206             Log.w(TAG, "Index needs to be rebuilt as build-version is not the same");
207             // We need to drop the tables and recreate them
208             reconstruct(db);
209         } else {
210             Log.i(TAG, "Index is fine");
211         }
212     }
213 
214     @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)215     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
216         if (oldVersion < DATABASE_VERSION) {
217             Log.w(TAG, "Detected schema version '" + oldVersion + "'. " +
218                     "Index needs to be rebuilt for schema version '" + newVersion + "'.");
219             // We need to drop the tables and recreate them
220             reconstruct(db);
221         }
222     }
223 
224     @Override
onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)225     public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
226         Log.w(TAG, "Detected schema version '" + oldVersion + "'. " +
227                 "Index needs to be rebuilt for schema version '" + newVersion + "'.");
228         // We need to drop the tables and recreate them
229         reconstruct(db);
230     }
231 
reconstruct(SQLiteDatabase db)232     public void reconstruct(SQLiteDatabase db) {
233         dropTables(db);
234         bootstrapDB(db);
235     }
236 
getBuildVersion(SQLiteDatabase db)237     private String getBuildVersion(SQLiteDatabase db) {
238         String version = null;
239         Cursor cursor = null;
240         try {
241             cursor = db.rawQuery(SELECT_BUILD_VERSION, null);
242             if (cursor.moveToFirst()) {
243                 version = cursor.getString(0);
244             }
245         } catch (Exception e) {
246             Log.e(TAG, "Cannot get build version from Index metadata");
247         } finally {
248             if (cursor != null) {
249                 cursor.close();
250             }
251         }
252         return version;
253     }
254 
255     /**
256      * Perform a full index on an OTA or when the locale has changed
257      *
258      * @param locale      is the default for the device
259      * @param fingerprint id for the current build.
260      * @return true when the locale or build has changed since last index.
261      */
262     @VisibleForTesting
isFullIndex(Context context, String locale, String fingerprint, String providerVersionedNames)263     static boolean isFullIndex(Context context, String locale, String fingerprint,
264             String providerVersionedNames) {
265         final boolean isLocaleIndexed = IndexDatabaseHelper.isLocaleAlreadyIndexed(context, locale);
266         final boolean isBuildIndexed = IndexDatabaseHelper.isBuildIndexed(context, fingerprint);
267         final boolean areProvidersIndexed = IndexDatabaseHelper
268                 .areProvidersIndexed(context, providerVersionedNames);
269 
270         return !(isLocaleIndexed && isBuildIndexed && areProvidersIndexed);
271     }
272 
273     @VisibleForTesting
buildProviderVersionedNames(List<ResolveInfo> providers)274     static String buildProviderVersionedNames(List<ResolveInfo> providers) {
275         StringBuilder sb = new StringBuilder();
276         for (ResolveInfo info : providers) {
277             sb.append(info.providerInfo.packageName)
278                     .append(':')
279                     .append(info.providerInfo.applicationInfo.versionCode)
280                     .append(',');
281         }
282         return sb.toString();
283     }
284 
clearCachedIndexed(Context context)285     static void clearCachedIndexed(Context context) {
286         context.getSharedPreferences(INDEX, Context.MODE_PRIVATE).edit().clear().commit();
287     }
288 
setLocaleIndexed(Context context, String locale)289     static void setLocaleIndexed(Context context, String locale) {
290         context.getSharedPreferences(INDEX, Context.MODE_PRIVATE)
291                 .edit()
292                 .putBoolean(locale, true)
293                 .apply();
294     }
295 
setProvidersIndexed(Context context, String providerVersionedNames)296     static void setProvidersIndexed(Context context, String providerVersionedNames) {
297         context.getSharedPreferences(INDEX, Context.MODE_PRIVATE)
298                 .edit()
299                 .putString(PREF_KEY_INDEXED_PROVIDERS, providerVersionedNames)
300                 .apply();
301     }
302 
isLocaleAlreadyIndexed(Context context, String locale)303     static boolean isLocaleAlreadyIndexed(Context context, String locale) {
304         return context.getSharedPreferences(INDEX, Context.MODE_PRIVATE).getBoolean(locale, false);
305     }
306 
areProvidersIndexed(Context context, String providerVersionedNames)307     static boolean areProvidersIndexed(Context context, String providerVersionedNames) {
308         final String indexedProviders = context.getSharedPreferences(INDEX, Context.MODE_PRIVATE)
309                 .getString(PREF_KEY_INDEXED_PROVIDERS, null);
310         return TextUtils.equals(indexedProviders, providerVersionedNames);
311     }
312 
isBuildIndexed(Context context, String buildNo)313     static boolean isBuildIndexed(Context context, String buildNo) {
314         return context.getSharedPreferences(INDEX, Context.MODE_PRIVATE).getBoolean(buildNo, false);
315     }
316 
setBuildIndexed(Context context, String buildNo)317     static void setBuildIndexed(Context context, String buildNo) {
318         context.getSharedPreferences(INDEX, 0).edit().putBoolean(buildNo, true).commit();
319     }
320 
dropTables(SQLiteDatabase db)321     private void dropTables(SQLiteDatabase db) {
322         clearCachedIndexed(mContext);
323         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_META_INDEX);
324         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_PREFS_INDEX);
325         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES);
326         db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SITE_MAP);
327     }
328 }
329